{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:23.104486Z",
     "start_time": "2025-02-09T03:56:20.955503Z"
    }
   },
   "source": [
    "# 导入库\n",
    "import matplotlib as mpl\n",
    "# Matplotlib 绘制的图形会直接嵌入到 Notebook 的输出单元格中，而不是弹出一个独立的窗口\n",
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "\n",
    "# os库提供了一种使用操作系统相关功能的便捷方式，允许与操作系统进行交互\n",
    "import os\n",
    "\n",
    "# sys库主要用于处理Python运行时的环境相关信息以及与解释器的交互\n",
    "import sys\n",
    "import time\n",
    "\n",
    "# tqdm库是一个快速，可扩展的Python进度条，可以在 Python 长循环中添加一个进度提示信息，用户只需要封装任意的迭代器 tqdm(iterator)，即可获得一个进度条显示迭代进度。\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "\n",
    "# torch.nn是PyTorch中用于构建神经网络的模块\n",
    "import torch.nn as nn\n",
    "\n",
    "# torch.nn.functional是PyTorch中包含了神经网络的一些常用函数\n",
    "import torch.nn.functional as F\n",
    "\n",
    "# Python 中的一个属性，用于获取当前 Python 解释器的版本信息。它返回一个命名元组（named tuple）\n",
    "print(sys.version_info)\n",
    "\n",
    "# 遍历模块，打印模块名称和版本信息，快速检查当前环境中安装的某些常用 Python 库的版本信息\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "# 判断是否有 GPU，如果有，则使用 GPU 进行训练，否则使用 CPU 进行训练\n",
    "# torch.cuda.is_available()用于判断是否有GPU\n",
    "# torch.device(\"cuda:0\")创建一个 PyTorch 设备对象，表示使用第一个 GPU（索引为 0）\n",
    "# torch.device(\"cpu\")创建一个 PyTorch 设备对象，表示使用 CPU\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.3.1+cu121\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 数据准备",
   "id": "8d3a035da6b77b68"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:23.847189Z",
     "start_time": "2025-02-09T03:56:23.105490Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 提供了许多常用的公开数据集\n",
    "from torchvision import datasets\n",
    "\n",
    "# 导入 ToTensor 类，它是一个图像变换工具，用于将 PIL 图像或 NumPy 数组转换为 PyTorch 张量（torch.Tensor）\n",
    "# 同时将像素值从 [0, 255] 缩放到 [0.0, 1.0]\n",
    "# 在加载数据集时，通常需要将图像转换为张量，以便输入到神经网络中\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# 图像预处理工具，用于对图像进行各种变换（如缩放、裁剪、旋转、归一化等）\n",
    "from torchvision import transforms\n",
    "from torch.utils.data import random_split\n",
    "\n",
    "# PyTorch 中用于定义图像预处理流水线的方式\n",
    "# transforms.Compose 将多个图像变换操作组合在一起，按顺序依次应用到图像上\n",
    "# 传入一个空列表 []，则表示不进行任何变换,定义一个空的变换流水线\n",
    "transform = transforms.Compose([ToTensor()])\n",
    "\n",
    "# 训练集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",  # 数据集存储路径\n",
    "    train=True,  # 加载训练集\n",
    "    download=True,  # 如果数据集不存在，则自动下载\n",
    "    transform=transform  # 对图像应用的变换\n",
    ")\n",
    "\n",
    "# 测试集\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",  # 数据集存储路径\n",
    "    train=True,  # 加载测试集\n",
    "    download=True,  # 如果数据集不存在，则自动下载\n",
    "    transform=transform  # 对图像应用的变换\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 这里用 random_split 按照 11 : 1 的比例来划分数据集\n",
    "# torch.Generator().manual_seed(seed) 设置随机种子，保证划分结果一致\n",
    "train_ds, val_ds = random_split(train_ds, [55000, 5000], torch.Generator().manual_seed(seed))"
   ],
   "id": "daa3b5bc1bda4d2b",
   "outputs": [],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.070786Z",
     "start_time": "2025-02-09T03:56:23.848192Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 计算数据集中所有图像的均值和标准差\n",
    "# ds：数据集对象，通常是一个可迭代对象，返回元组 (img, label)\n",
    "def cal_mean_std(ds):\n",
    "    # 初始化均值和标准差的累加器\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "\n",
    "    # 对每个图像的每个通道计算均值，然后对所有图像求平均\n",
    "\n",
    "    # 遍历每张图片,_ 是标签（未使用）\n",
    "    for img, _ in ds:\n",
    "        # 计算每张图片的均值和方差,dim=(1, 2) 表示在高度和宽度维度上计算均值。\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "\n",
    "    # 将累加的均值和标准差除以数据集大小，得到整个数据集的均值和标准差\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "print(cal_mean_std(train_ds))"
   ],
   "id": "bf47e2fba09b3fe6",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.2856]), tensor([0.3202]))\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.075375Z",
     "start_time": "2025-02-09T03:56:27.071291Z"
    }
   },
   "cell_type": "code",
   "source": [
    "img, label = train_ds[0]\n",
    "img.shape, label"
   ],
   "id": "cb8f020a949a3d62",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(torch.Size([1, 28, 28]), 9)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.080379Z",
     "start_time": "2025-02-09T03:56:27.076380Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data.dataloader import DataLoader\n",
    "\n",
    "batch_size = 32\n",
    "# 从数据集到dataloader\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)\n",
    "val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False, num_workers=4)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ],
   "id": "9b2533155acfbdbb",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 模型定义",
   "id": "635510a2ccba05a2"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "功能: 定义了一个卷积神经网络（CNN）模型，用于图像分类任务。\n",
    "\n",
    "原理:\n",
    "\n",
    "使用卷积层提取图像的局部特征。\n",
    "\n",
    "使用池化层降低特征图的尺寸。\n",
    "\n",
    "使用全连接层将提取的特征映射到最终的类别数。\n",
    "\n",
    "作用:\n",
    "\n",
    "适用于图像分类任务（如 MNIST、CIFAR-10 等）。\n",
    "\n",
    "能够自动提取图像的特征，并进行分类。\n",
    "\n",
    "为什么这样做:\n",
    "\n",
    "卷积神经网络是图像分类任务中最常用的模型之一。\n",
    "\n",
    "通过参数初始化和多层卷积操作，增强模型的表达能力和训练稳定性。"
   ],
   "id": "1520b1d45d28038f"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.086739Z",
     "start_time": "2025-02-09T03:56:27.080379Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 CNN 的类，继承自 nn.Module\n",
    "class CNN(nn.Module):\n",
    "\n",
    "    # 定义类的构造函数，接受一个参数 activation，表示激活函数的类型，默认值为 \"relu\"\n",
    "    # 初始化模型的层结构和参数\n",
    "    def __init__(self, activation=\"relu\"):\n",
    "        super().__init__()\n",
    "\n",
    "        # 根据 activation 参数选择激活函数，F.relu 或 F.selu\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "\n",
    "        # 定义两个卷积层，输入通道数分别为 1 和 32，输出通道数均为 32，卷积核大小为 3x3，填充为 1。\n",
    "        # 卷积层用于提取图像的局部特征,通过卷积操作提取图像的底层特征\n",
    "        # 提取输入图像的初级特征，输出通道数增加到 32\n",
    "        # 输入：(batch_size, 1, 28, 28)，输出：(batch_size, 32, 28, 28)\n",
    "        self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1)\n",
    "        # 输入：(batch_size, 32, 28, 28)，输出：(batch_size, 32, 28, 28)\n",
    "        # 进一步提取特征，通道数保持不变\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=1)\n",
    "\n",
    "        # 定义一个最大池化层，池化核大小为 2x2，步幅为 2\n",
    "        # 池化层用于降低特征图的空间维度，减少计算量\n",
    "        # 通过池化操作减少特征图的尺寸，同时保留重要特征\n",
    "        # 输入：(batch_size, 32, 28, 28)，输出：(batch_size, 32, 14, 14)\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "\n",
    "        # 定义两个卷积层，输入通道数分别为 32 和 64，输出通道数均为 64，卷积核大小为 3x3，填充为 1。\n",
    "        # 卷积层用于提取图像的局部特征,通过卷积操作提取图像的中层特征\n",
    "        # 输入：(batch_size, 32, 14, 14)，输出：(batch_size, 64, 14, 14)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)\n",
    "        # 输入：(batch_size, 64, 14, 14)，输出：(batch_size, 64, 14, 14)\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1)\n",
    "\n",
    "        # 输入：(batch_size, 64, 14, 14)，输出：(batch_size, 64, 7, 7)\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "\n",
    "        # 定义两个卷积层，输入通道数分别为 64 和 128，输出通道数均为 128，卷积核大小为 3x3，填充为 1。\n",
    "        # 卷积层用于提取图像的局部特征,通过卷积操作提取图像的高层特征\n",
    "        # 输入：(batch_size, 64, 7, 7)，输出：(batch_size, 128, 7, 7)\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1)\n",
    "        # 输入：(batch_size, 128, 7, 7)，输出：(batch_size, 128, 7, 7)\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=1)\n",
    "\n",
    "        # 输入：(batch_size, 128, 7, 7)，输出：(batch_size, 128, 3, 3)\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "\n",
    "        # 定义一个展平层，用于将多维特征图展平为一维向量\n",
    "        self.flatten = nn.Flatten()\n",
    "\n",
    "        # 定义一个全连接层，输入维度为 128 * 3 * 3，输出维度为 128。\n",
    "        # 全连接层用于将提取的特征映射到高维空间\n",
    "        self.fc1 = nn.Linear(128 * 3 * 3, 128)\n",
    "\n",
    "        # 输出层将特征映射到最终的类别数\n",
    "        self.fc2 = nn.Linear(128, 10)\n",
    "\n",
    "        # 调用 init_weights 方法初始化模型参数\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    #  定义前向传播方法，接受输入数据 x，并获取激活函数 act\n",
    "    def forward(self, x):\n",
    "        act = self.activation\n",
    "\n",
    "        # 将输入数据 x 依次通过 conv1、激活函数、conv2、激活函数和池化层\n",
    "        # 提取图像的底层特征，并通过池化层降低特征图的尺寸。减少计算量。\n",
    "        x = self.pool(act(self.conv2(act(self.conv1(x)))))\n",
    "\n",
    "        # 将降低尺寸后的特征图依次通过 conv3、激活函数、conv4、激活函数和池化层\n",
    "        # 提取图像的中层特征，并通过池化层降低特征图的尺寸。减少计算量。\n",
    "        x = self.pool(act(self.conv4(act(self.conv3(x)))))\n",
    "\n",
    "        # 将降低尺寸后的特征图依次通过 conv5、激活函数、conv6、激活函数和池化层\n",
    "        # 提取图像的高层特征，并通过池化层降低特征图的尺寸。减少计算量。\n",
    "        x = self.pool(act(self.conv6(act(self.conv5(x)))))\n",
    "\n",
    "        # 将降低尺寸后的特征图展平为一维向量，输入全连接层\n",
    "        x = self.flatten(x)\n",
    "\n",
    "        # 将展平后的特征向量通过全连接层 fc1 和激活函数。\n",
    "        x = act(self.fc1(x))\n",
    "\n",
    "        # 将特征向量通过全连接层 fc2，得到最终的输出\n",
    "        x = self.fc2(x)\n",
    "        return x"
   ],
   "id": "55461365260db39b",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.094137Z",
     "start_time": "2025-02-09T03:56:27.086739Z"
    }
   },
   "cell_type": "code",
   "source": [
    "for idx, (key, value) in enumerate(CNN().named_parameters()):\n",
    "    print(f\"{key}\\tparamerters num: {np.prod(value.shape)}\")  # 打印模型的参数信息"
   ],
   "id": "173c7ecf8b190e4e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight\tparamerters num: 288\n",
      "conv1.bias\tparamerters num: 32\n",
      "conv2.weight\tparamerters num: 9216\n",
      "conv2.bias\tparamerters num: 32\n",
      "conv3.weight\tparamerters num: 18432\n",
      "conv3.bias\tparamerters num: 64\n",
      "conv4.weight\tparamerters num: 36864\n",
      "conv4.bias\tparamerters num: 64\n",
      "conv5.weight\tparamerters num: 73728\n",
      "conv5.bias\tparamerters num: 128\n",
      "conv6.weight\tparamerters num: 147456\n",
      "conv6.bias\tparamerters num: 128\n",
      "fc1.weight\tparamerters num: 147456\n",
      "fc1.bias\tparamerters num: 128\n",
      "fc2.weight\tparamerters num: 1280\n",
      "fc2.bias\tparamerters num: 10\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:27.314229Z",
     "start_time": "2025-02-09T03:56:27.094137Z"
    }
   },
   "cell_type": "code",
   "source": [
    "activation = \"relu\"\n",
    "model = CNN(activation)\n",
    "model.to(device)\n",
    "img = torch.randn(1, 1, 28, 28).to(device)\n",
    "model(img)"
   ],
   "id": "53957406e5034207",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[-0.1220,  0.0194,  0.0106, -0.0171, -0.0892, -0.0747, -0.0886, -0.1283,\n",
       "          0.0574, -0.0040]], device='cuda:0', grad_fn=<AddmmBackward0>)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 模型变体",
   "id": "3c656be45ed08dbc"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "#练习不同尺寸的卷积核，padding，stride的效果\n",
    "class CNN1(nn.Module):\n",
    "    def __init__(self, activation=\"relu\"):\n",
    "        super().__init__()\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "        #输入通道数，图片是灰度图，所以是1，图片是彩色图，就是3，输出通道数，就是卷积核的个数（32,1,28,28）\n",
    "        # paddind=2 卷积核的边缘补2个0，stride=2 步长为2\n",
    "        # 输入(batch_size,1,28,28) 输出(batch_size,32,14,14)\n",
    "        self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=5, padding=2, stride=2)\n",
    "        # 输入(batch_size,32,14,14) 输出(batch_size,32,14,14)\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,32,14,14) 输出(batch_size,32,7,7)\n",
    "        self.pool = nn.MaxPool2d(2, 2)  #池化核大小为2（2*2），步长为2\n",
    "\n",
    "        # 输入(batch_size,32,7,7) 输出(batch_size,64,7,7)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,64,7,7) 输出(batch_size,64,7,7)\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,64,7,7) 输出(batch_size,128,7,7)\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,128,7,7) 输出(batch_size,128,7,7)\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=1)\n",
    "        # 输入(batch_size,128,7,7) 输出(batch_size,128,3,3)\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "\n",
    "        self.flatten = nn.Flatten()\n",
    "\n",
    "        self.fc1 = nn.Linear(128 * 3 * 3, 128)\n",
    "        self.fc2 = nn.Linear(128, 10)  #输出尺寸（32,10）\n",
    "\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x):\n",
    "        # 定义激活函数\n",
    "        act = self.activation\n",
    "        # 两次卷积，一次池化，两次卷积，一次池化，展平，全连接，输出\n",
    "        x = act(self.conv1(x))\n",
    "        x = act(self.conv2(x))\n",
    "        x = self.pool(x)\n",
    "\n",
    "        x = act(self.conv3(x))\n",
    "        x = act(self.conv4(x))\n",
    "        x = self.pool(x)\n",
    "\n",
    "        x = act(self.conv5(x))\n",
    "        x = act(self.conv6(x))\n",
    "        x = self.pool(x)\n",
    "\n",
    "        x = self.flatten(x)\n",
    "        x = act(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return x\n"
   ],
   "id": "752742e471a3fa17"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "目的: 可视化 PyTorch 模型的计算图，帮助理解模型的结构和数据流动。\n",
    "\n",
    "步骤:\n",
    "\n",
    "创建虚拟输入张量。\n",
    "\n",
    "运行模型，得到输出。\n",
    "\n",
    "使用 make_dot 生成计算图。\n",
    "\n",
    "将计算图保存为 PNG 文件。\n",
    "\n",
    "输出: 生成一个名为 model_CNN.png 的图像文件，包含模型的计算图。"
   ],
   "id": "ca1ff14603858c4b"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:56:28.043709Z",
     "start_time": "2025-02-09T03:56:27.314229Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 从 torchviz 库中导入 make_dot 函数\n",
    "# make_dot 用于生成 PyTorch 模型的计算图\n",
    "from torchviz import make_dot\n",
    "\n",
    "# 创建一个虚拟输入张量，形状为 (1, 1, 28, 28)\n",
    "# 需要一个输入张量来运行模型并生成计算图\n",
    "dummy_input = torch.randn(1, 1, 28, 28).to(device)\n",
    "\n",
    "# 将虚拟输入张量传递给模型，得到输出\n",
    "# 计算图是基于模型的前向传播过程生成的，因此需要运行模型\n",
    "output = model(dummy_input).to(device)\n",
    "\n",
    "# 生成模型的计算图\n",
    "# output: 模型的输出张量\n",
    "# params: 模型的参数（通过 model.named_parameters() 获取）\n",
    "# make_dot 使用输出张量和模型参数来构建计算图\n",
    "dot = make_dot(output, params=dict(model.named_parameters()))\n",
    "\n",
    "# 生成一个名为 model_CNN.png 的文件\n",
    "dot.render(\"model_CNN\", format=\"png\")"
   ],
   "id": "9fa1e9435257f8ea",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'model_CNN.png'"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 模型训练",
   "id": "e08bc96e8c6ba14"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:58:03.800069Z",
     "start_time": "2025-02-09T03:58:01.119475Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir,\n",
    "                                    flush_secs=flush_secs)  # 实例化SummaryWriter, log_dir是log存放路径，flush_secs是每隔多少秒写入磁盘\n",
    "\n",
    "    def draw_model(self, model, input_shape):  #graphs\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))  # 画模型图\n",
    "\n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\",\n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "        )  # 画loss曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "\n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )  # 画acc曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "\n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "        )  # 画lr曲线, main_tag是主tag，tag_scalar_dict是子tag，global_step是步数\n",
    "\n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss,把loss，val_loss取掉，画loss曲线\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)  # 画loss曲线\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)  # 画acc曲线\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)  # 画lr曲线\n"
   ],
   "id": "bf751873e58d2379",
   "outputs": [],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:58:05.803611Z",
     "start_time": "2025-02-09T03:58:05.799385Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 SaveCheckpointsCallback 的类。\n",
    "# 该类用于在训练过程中保存模型的检查点。封装保存检查点的逻辑，方便在训练过程中调用\n",
    "class SaveCheckpointsCallback:\n",
    "    \"\"\"\n",
    "    Callback to save model checkpoints during training.\n",
    "    \"\"\"\n",
    "\n",
    "    #定义类的构造函数，接受以下参数：\n",
    "    # save_dir: 检查点保存的目录。\n",
    "    # save_step: 每隔多少步保存一次检查点，默认值为 500。\n",
    "    # save_best_only: 是否只保存性能最好的检查点，默认值为 True\n",
    "    # 原理：初始化回调类的参数和状态\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "\n",
    "        # 将传入的参数保存为类的属性\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "\n",
    "        # 初始化一个变量 best_metrics，用于记录当前最好的性能指标值，初始值为 -1\n",
    "        # 原理: 用于比较当前模型的性能是否优于之前保存的模型,当 save_best_only 为 True 时，只保存性能最好的模型\n",
    "        self.best_metrics = -1\n",
    "\n",
    "        # 检查保存目录是否存在，如果不存在则创建该目录\n",
    "        # os.path.exists()用于判断路径是否存在, os.mkdir()用于创建目录\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "\n",
    "    # 定义 __call__ 方法，使类的实例可以像函数一样调用。参数包括：\n",
    "    # step: 当前训练的步数。\n",
    "    # state_dict: 模型的状态字典（包含模型参数）。\n",
    "    # metric: 当前模型的性能指标值（如验证集准确率），默认为 None\n",
    "    # 在训练过程中定期调用该方法，保存检查点\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "\n",
    "        # 检查当前步数是否是 save_step 的倍数，如果不是则直接返回\n",
    "        # 控制保存检查点的频率，避免频繁保存\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "\n",
    "        # 检查是否只保存性能最好的检查点\n",
    "        # 根据 save_best_only 的值决定保存策略,提供两种保存策略：定期保存和只保存最佳模型\n",
    "        if self.save_best_only:\n",
    "\n",
    "            # 如果 save_best_only 为 True，则要求 metric 不能为 None。\n",
    "            # 原理: 使用 assert 断言确保 metric 有值。如果 metric 为 None，无法判断模型性能是否更好，因此需要提前检查\n",
    "            assert metric is not None\n",
    "\n",
    "            # 检查当前模型的性能指标是否优于之前保存的最佳模型\n",
    "            # 只保存性能更好的模型，避免保存性能下降的模型\n",
    "            if metric >= self.best_metrics:\n",
    "                # 将模型的状态字典保存到指定目录下的 best.ckpt 文件中\n",
    "                # 使用 torch.save 保存模型参数。保存当前性能最好的模型，方便后续加载和使用\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "\n",
    "                # 更新 best_metrics 为当前模型的性能指标值\n",
    "                self.best_metrics = metric\n",
    "\n",
    "        # 如果 save_best_only 为 False，则执行以下逻辑\n",
    "        # 定期保存检查点，不关心模型性能是否更好\n",
    "        else:\n",
    "\n",
    "            # 将模型的状态字典保存到指定目录下，文件名为当前步数\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))"
   ],
   "id": "68efe425b70740a3",
   "outputs": [],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:58:10.414222Z",
     "start_time": "2025-02-09T03:58:10.410769Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 EarlyStopCallback 的类.用于监控模型在验证集上的性能，并在性能不再提升时触发早停。\n",
    "# 封装早停的逻辑，方便在训练过程中调用\n",
    "class EarlyStopCallback:\n",
    "\n",
    "    # 定义类的构造函数，接受以下参数\n",
    "    # patience: 容忍的轮次数，默认值为 5。如果在 patience 轮内性能没有提升，则触发早停。\n",
    "    # min_delta: 性能提升的最小阈值，默认值为 0.01。只有当性能提升超过该阈值时，才认为模型有改进。\n",
    "    # 初始化早停回调的参数和状态\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "\n",
    "        # 将传入的参数保存为类的属性\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "\n",
    "        self.best_metric = -1  # 用于比较当前模型的性能是否优于之前的最佳性能\n",
    "\n",
    "        # 初始化一个计数器 counter，用于记录性能没有提升的连续轮次数，初始值为 0\n",
    "        # 当性能没有提升时，计数器增加；当性能提升时，计数器重置,用于判断是否达到了早停的条件\n",
    "        self.counter = 0\n",
    "\n",
    "    # 训练过程中定期调用该方法，更新早停状态\n",
    "    def __call__(self, metric):\n",
    "\n",
    "        # 检查当前性能指标是否比之前的最佳性能提升了至少 min_delta\n",
    "        # 避免微小的波动触发早停,当性能有显著提升时，才认为模型有改进\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "\n",
    "            self.best_metric = metric  # 记录当前最佳性能\n",
    "            self.counter = 0  # 性能有提升时，重置计数器,重新开始计算性能没有提升的连续轮次数\n",
    "\n",
    "        # 性能没有提升时，增加计数器\n",
    "        else:\n",
    "            self.counter += 1  # 记录性能没有提升的连续轮次数\n",
    "\n",
    "    # 定义一个只读属性 early_stop，用于判断是否触发早停。\n",
    "    # 使用 @property 装饰器将方法转换为属性。方便外部代码通过属性访问早停状态。\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "\n",
    "        # 检查计数器 counter 是否大于或等于 patience\n",
    "        # 提供早停的触发条件\n",
    "        return self.counter >= self.patience"
   ],
   "id": "e1260d4d3d629a34",
   "outputs": [],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T03:58:31.841849Z",
     "start_time": "2025-02-09T03:58:31.788848Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "# 这是一个装饰器，用于禁用梯度计算。\n",
    "# 在评估模型时，我们不需要计算梯度，因为不会进行反向传播和参数更新。禁用梯度计算可以减少内存消耗并加速计算。\n",
    "@torch.no_grad()\n",
    "# 定义评估函数，接受三个参数：model（要评估的模型）、dataloader（数据加载器，用于提供评估数据）、loss_fct（损失函数）。\n",
    "# 用于评估模型在给定数据集上的性能。\n",
    "# 通过封装评估过程，可以方便地在不同模型和数据集上重复使用。\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    # 初始化三个空列表，分别用于存储每个批次的损失、预测标签和真实标签。\n",
    "    # 用于累积所有批次的损失和标签，以便后续计算平均损失和准确率。\n",
    "    # 累积所有批次的结果可以更准确地反映模型在整个数据集上的性能。\n",
    "    loss_list = []  # 记录损失\n",
    "    pred_list = []  # 记录预测\n",
    "    label_list = []  # 记录标签\n",
    "\n",
    "    # 遍历数据加载器中的每个批次，datas 是输入数据，labels 是对应的真实标签\n",
    "    for datas, labels in dataloader:\n",
    "        # 将输入数据和标签移动到指定的设备（GPU或CPU），转到GPU可以加速计算\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 将输入数据传递给模型，得到模型的输出 logits（通常是未归一化的预测分数）。\n",
    "        # 模型的前向传播过程，计算输入数据对应的输出。通过模型的输出可以计算损失和预测标签。\n",
    "        logits = model(datas)\n",
    "\n",
    "        # 将当前批次的损失值（转换为 Python 浮点数）添加到 loss_list 中。\n",
    "        # loss.item() 将张量中的单个值提取为 Python 浮点数。\n",
    "        # 累积所有批次的损失值，以便后续计算平均损失。\n",
    "        loss = loss_fct(logits, labels)\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 通过 argmax 函数获取模型预测的类别标签。axis=-1 表示在最后一个维度上取最大值对应的索引。\n",
    "        # logits 通常是每个类别的分数，argmax 找到分数最高的类别作为预测结果。\n",
    "        # 将模型的输出转换为具体的类别标签，便于与真实标签进行比较。\n",
    "        preds = logits.argmax(axis=-1)\n",
    "\n",
    "        # 将预测标签从 GPU 移动到 CPU，并转换为 NumPy 数组，再转换为 Python 列表，然后添加到 pred_list 中。\n",
    "        # preds.cpu().numpy().tolist() 将张量转换为 Python 列表。\n",
    "        # 累积所有批次的预测标签，以便后续计算准确率。\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    #计算预测标签和真实标签之间的准确率\n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "\n",
    "    #返回所有批次的平均损失和准确率。\n",
    "    return np.mean(loss_list), acc"
   ],
   "id": "e909466dad00184a",
   "outputs": [],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T04:53:42.277531Z",
     "start_time": "2025-02-09T04:53:42.164988Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 training 的函数，用于训练模型。参数包括：\n",
    "# model: 要训练的模型。\n",
    "# train_loader: 训练数据的数据加载器。\n",
    "# val_loader: 验证数据的数据加载器。\n",
    "# epoch: 训练的轮数。\n",
    "# loss_fct: 损失函数。\n",
    "# optimizer: 优化器。\n",
    "# eval_step: 每隔多少步评估一次验证集性能，默认值为 500。\n",
    "# 该函数通过遍历训练数据，更新模型参数，并定期评估验证集性能。封装训练过程，方便重复使用\n",
    "def training(\n",
    "        model,\n",
    "        train_loader,\n",
    "        val_loader,\n",
    "        epoch,\n",
    "        loss_fct,\n",
    "        optimizer,\n",
    "        tensorboard_callback=None,\n",
    "        save_ckpt_callback=None,\n",
    "        early_stop_callback=None,\n",
    "        eval_step=500,\n",
    "):\n",
    "    # 初始化一个字典 record_dict，用于记录训练和验证过程中的损失和准确率。\n",
    "    # 通过记录训练和验证的性能，可以分析模型的训练过程。方便后续绘制训练曲线或分析模型表现。\n",
    "    record_dict = {\n",
    "        \"train\": [],  # \"train\": 存储训练集的损失和准确率。\n",
    "        \"val\": []  # \"val\": 存储验证集的损失和准确率。\n",
    "    }\n",
    "\n",
    "    # 初始化一个全局步数计数器 global_step，用于记录当前训练的步数。\n",
    "    # 步数用于控制何时评估验证集性能。通过步数而不是轮数来控制评估频率，更灵活。\n",
    "    global_step = 0\n",
    "\n",
    "    # 将模型设置为训练模式。在训练模式下，模型会启用一些特定于训练的功能（如 Dropout 和 BatchNorm）。\n",
    "    # 确保模型在训练时行为正确。\n",
    "    model.train()\n",
    "\n",
    "    # 使用 tqdm 创建一个进度条，总长度为 epoch * len(train_loader)，即总训练步数。\n",
    "    # tqdm 是一个进度条库，用于显示训练进度。\n",
    "    # 提供可视化的训练进度，方便监控训练过程。\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "\n",
    "        # 外层循环，遍历每个训练轮次。每个轮次会遍历整个训练数据集一次。\n",
    "        # 多轮训练可以提高模型的性能。\n",
    "        for epoch_id in range(epoch):\n",
    "\n",
    "            # 内层循环，遍历训练数据加载器中的每个批次。\n",
    "            for datas, labels in train_loader:\n",
    "\n",
    "                # 将输入数据和标签移动到指定的设备（GPU或CPU），转到GPU可以加速计算\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 清除优化器中之前的梯度。\n",
    "                # PyTorch 会累积梯度，因此在每次反向传播前需要清除之前的梯度。避免梯度累积导致错误的参数更新。\n",
    "                optimizer.zero_grad()\n",
    "\n",
    "                logits = model(datas)  # 模型前向计算\n",
    "                loss = loss_fct(logits, labels)  # 计算损失\n",
    "\n",
    "                # 计算损失相对于模型参数的梯度。\n",
    "                # 反向传播算法，通过链式法则计算梯度。梯度用于更新模型参数，以最小化损失。\n",
    "                loss.backward()\n",
    "\n",
    "                # 使用优化器更新模型参数。\n",
    "                optimizer.step()\n",
    "\n",
    "                preds = logits.argmax(axis=-1)  # 通过 argmax 函数获取模型预测的类别标签。\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())  # 计算当前批次的准确率\n",
    "                loss = loss.cpu().item()  # 将损失值从 GPU 移动到 CPU，并转换为 Python 浮点数。方便记录和打印损失值。\n",
    "\n",
    "                # 将当前批次的损失、准确率和步数记录到 record_dict[\"train\"] 中\n",
    "                # 累积训练过程中的性能指标。方便后续分析训练过程\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 每隔 eval_step 步评估一次验证集性能。\n",
    "                # 使用 global_step 控制评估频率。定期评估验证集性能，避免过拟合。\n",
    "                if global_step % eval_step == 0:\n",
    "                    # 将模型设置为评估模式\n",
    "                    model.eval()\n",
    "\n",
    "                    # 调用 evaluating 函数计算验证集的损失和准确率\n",
    "                    # 在验证集上评估模型性能。验证集性能反映了模型的泛化能力。\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "\n",
    "                    # 将验证集的损失、准确率和步数记录到 record_dict[\"val\"] 中。\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "\n",
    "                    # 将模型设置为训练模式\n",
    "                    model.train()\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        # 调用 tensorboard 回调函数，记录训练过程中的关键指标\n",
    "                        tensorboard_callback(\n",
    "                            global_step,  # 当前训练的全局步数，用于在 TensorBoard 中标识不同的训练阶段\n",
    "                            loss=loss, val_loss=val_loss,  # 记录训练集和验证集的损失值，用于监控模型在训练和验证集上的表现\n",
    "                            acc=acc, val_acc=val_acc,  # 记录训练集和验证集的准确率，用于监控模型的分类性能\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],  # 取出当前学习率，用于监控学习率的变化，确保学习率调整策略正常工作\n",
    "                        )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # 调用保存模型权重的回调函数，保存当前模型的权重\n",
    "                        save_ckpt_callback(\n",
    "                            global_step,  # 当前训练的全局步数，用于标识保存的模型权重对应的训练阶段\n",
    "                            model.state_dict(),  # 保存模型的当前状态字典（即模型的所有参数），以便后续可以恢复模型\n",
    "                            metric=val_acc  # 使用验证集的准确率作为指标，判断是否保存当前模型（通常只保存性能最好的模型）\n",
    "                        )\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 调用早停回调函数，监控验证集准确率是否不再提升\n",
    "                        early_stop_callback(val_acc)  # 传入验证集准确率，用于判断是否满足早停条件\n",
    "                        if early_stop_callback.early_stop:  # 检查是否触发了早停条件\n",
    "                            # 如果触发了早停条件，打印提示信息并结束训练\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 返回训练记录字典，包含训练过程中的关键指标\n",
    "\n",
    "                # 增加全局步数计数器\n",
    "                # 记录当前训练的步数,控制评估频率和记录训练进度\n",
    "                global_step += 1\n",
    "\n",
    "                # 更新进度条\n",
    "                pbar.update(1)\n",
    "\n",
    "                # 在进度条中显示当前训练的轮次。\n",
    "                # 更新进度条的后缀信息。方便监控训练进度。\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    # 返回记录的训练和验证性能的字典,包含训练和验证过程中的损失和准确率\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 20\n",
    "\n",
    "activation = \"relu\"\n",
    "model = CNN(activation)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/cnn-{activation}\")\n",
    "tensorboard_callback.draw_model(model, [1, 1, 28, 28])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/cnn-{activation}\", save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10)"
   ],
   "id": "647bd9bb695bc591",
   "outputs": [],
   "execution_count": 31
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T05:03:16.989443Z",
     "start_time": "2025-02-09T04:53:50.078999Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=1000\n",
    ")"
   ],
   "id": "81b211c47663873b",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/34380 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "9631aa4f243f40299e04ecf57274d00d"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 19 / global_step 33000\n"
     ]
    }
   ],
   "execution_count": 32
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T05:03:17.512465Z",
     "start_time": "2025-02-09T05:03:16.994740Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{int(x / 1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "id": "8a0818ea3f4cc42d",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzoAAAHACAYAAABqJx3iAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAyZlJREFUeJzsnQd8W+X5/Y9keceO4zi2M5y9dyAkJGEFMiDs0bJaRoEWCv0zSlughTJa6ILSMguUAi1lz18JISEQICSQkL134kw7XvEesvT/PO+9r3wlS7Zsy7Zkne/ncyNb1rhXcqz33PM857G53W43CCGEEEIIIaQLYe/sHSCEEEIIIYSQUEOhQwghhBBCCOlyUOgQQgghhBBCuhwUOoQQQgghhJAuB4UOIYQQQgghpMtBoUMIIYQQQgjpclDoEEIIIYQQQrocFDqEEEIIIYSQLocDEYDL5cKhQ4eQkpICm83W2btDCCFRg8yULisrQ58+fWC389yYhp9LhBAS/p9NESF05MMkJyens3eDEEKilv3796Nfv36dvRthAz+XCCEk/D+bIkLoyBkzfTCpqaktvn9dXR0WLlyIOXPmIDY2FpFGpO9/VziGSN//rnAMkb7/kXoMpaWlakGv/w4Tg2j/XBJ4DJ1PpO9/VziGSN//rv7ZFBFCR5cFyIdJaz9QkpKS1H0j5Q3sSvvfFY4h0ve/KxxDpO9/pB8Dy7O8ifbPJYHH0PlE+v53hWOI9P3v6p9NLLgmhBBCCCGEdDkodAghhBBCCCFdDgodQgghhBBCSJcjInp0CCHhG+/odDpRX1/fITXEDocD1dXVHfJ80XIMMTExap/Yg0MIIaSrQaFDCGkVtbW1OHz4MCorKztMVGVnZ6uUq0hdlIfrMUgTau/evREXF9fZu0IIIYSEDAodQkirhiXu2bNHuQEyrEsWyO29cJfnLC8vR7du3SJ2cGW4HYMILxGsR48eVe/nsGHDwmK/CCGEkFBAoUMIaTGyOJZFu2TYixvQEcjzyfMmJCRE7GI8HI8hMTFRxYnu27fPs2+EEEJIVyA8PmkJIRFJuCzWSdvg+0gIIaQrwk83QgghhBBCSJeDQocQQgghhBDS5aDQIYSQVjJw4EA8/vjjIXmsJUuWqECHkpKSkDxeNPHll1/i3HPPVcEY8hq+//77Qb3exx13HOLj4zF06FC89NJLHbKvhBBCOg4KHUJIVHHaaafhtttuC8ljrVy5Ej/+8Y9D8lik9VRUVGDChAl46qmngrq9JMydffbZmDlzJtauXat+H66//np88skn7b6vhBBCOg6mrhFCiE/ksgzzlCGazdGrV68O2SfSNGeddZbaguXZZ5/FoEGD8Oijj6rvR40ahaVLl+Kvf/0r5s6d2457SgghRKiuq0dCbAzamy4vdL7eWYCH/m8TkuvtmNfZO0NIFxYHVXX17R7NXFVbD0et0yslLDE2JugZPtdccw2++OILtf3tb39T1/3rX//Ctddei/nz5+M3v/kNNmzYgIULF6ro7DvuuAPffPONcgxkMfzII49g1qxZXqVr4gZoh0j24/nnn8dHH32k3IG+ffuqxfR5553XqmN+5513cN9992Hnzp1qoOfPfvYz/PznP/f8/Omnn1aLcxlA2r17d5x88sl4++231c/k8oEHHlD3lQjwSZMm4YMPPkBycjKineXLl3u9j4IInKacvpqaGrVpSktL1WVdXZ3aWoq+T2vuGy7wGMJv/+tdbvzinQ0YkZWCn5wyqF2e84Wle7Hh4DH8+eJxiHO0rTBoR145fjd/C05I7Nz3YPPhUvzxk+24Y9YwTOjXPeDt3l97CF9sL8DDF4xBYlyM3/fA5XLj/v9tQc/kONx6xtB22d/XVu7Hij3F+N35o5Ec738pX+t0qf0Y2isZP5oxMKz+H+wvrsT5T3+DCyb2wa/PGoEYe8vn8AW7r11e6LiL9mBywXuwx6cAOLOzd4eQLomInNH3dU7Zz+YH5yIpLrg/ZSJutm/fjrFjx+LBBx9U123atEld3nXXXfjLX/6CwYMHo0ePHko8zJs3D7///e9VH8crr7yi+kC2bduG/v37B3wOERd/+tOf8Oc//xlPPPEErrzySjWjJj09vUXHtWrVKnz/+9/H/fffj0svvRTLli3DT3/6U/Ts2VMJtu+++w7/7//9P/z73//G9OnTUVRUhK+++krd9/Dhw7j88svVflx44YUoKytTPxNBSoAjR44gKyvL6zr5XsRLVVWVmi3ki4hceW99EVHclllSixYtQqTDYwif/d9fDvzfBgcW2g8jp3xLuzzX31fEoKrehgH1hzAyrW1/U/5vnx3LDtlR18uOwZ34Hry9245leXa4ypbj8iGugLd7ZHUMimps6FF1EJN7uf2+B/vKgdc2GJ9JiUXb0S/E55ZcbuCRlcZ7EFN6EKf38f8erMi34a1dMYizu5FVshnBnA/sqP8H7+21o6zajhVb9uIT2+5WPUZlZWVQt+vyQietdBt+F/svrHcNA3BfZ+8OIaQTEdcjLi5OLUyzs7PVdVu3blWXInxmz57tua0IE+n70Dz00EN477338OGHH+KWW24J+BwiQkRkCA8//DD+/ve/Y8WKFTjzzJadaHnsscdwxhln4N5771XfDx8+HJs3b1YCSp4jNzdXuTPnnHMOUlJSMGDAAOXaaKHjdDpx0UUXqeuFcePGtej5iTd33323cvg0IorE9ZszZw5SU1NbdTZSFhXyOycDWyMRHkP47f+nW/KBDWtR47Jh5qy5HtchVJRVO1G1/DP1dWzv4Zg3c0ibHm/xWxuAQ4dRUoNOfQ/efGkVkFcIZ2I65s2bErDU6rZvFhvfZAzCvHkj/b4Hr3yTC2wwPld22HLw43mh/du7I78cVd8sU1+vLEnGH649CY4Yb2dNTmo989RyAOWoddlw6qw56BbA+fF3DO2J/A7d85cvxH/EL8+fjFOGZbTqcbSrjmgXOrY440xbPGo7e1cI6bJI+Zg4K+1dulZWWoaU1JRGpWuhYPLkyV7fl5eXKzdFytC0cJCz/SIwmmL8+PGer0WIyCI4Pz+/xfuzZcsWnH/++V7XzZgxQ6W8SQ+RfCCJiBEHSkSUbOLeiIgTgSYiScSNlGTJYvySSy5RThWBErl5eXle18n38l75c3MEcfVk80UWBW1ZGLT1/uEAjyF89j+/vKGc51iNC6nJCSF9nsKias/X6w6Utvk1O1JmlIOW1No69T3YW2i4A3sLqwLuw+7CamhTfN2BY41up/dfXhfNRxuP4O6zRyMrNXTvw4ZDZZ6vDx2rxuLthThnfB+v2yzbWYCteeWe749Vu9CjW/OvbUe8B+9+sx8VNfUYmtkNp4/KDrr03Jdg99MeLUIngUKHkHZD/lBJ+Vh7b3J20ve61v6R9MW3d+XOO+9UDo64MlL2JelcIhxqa2tb9MdX9k9EWqgRF2f16tV47bXXVP+O9PKIwJF46piYGHV27uOPP8bo0aNVCd2IESNU2hgBpk2bhsWLzTOzJvJ6yfWERDKHjzUIkYLymnZ9/DW5xaofpS0cMR+vpNZwIToDcWoOHavyvGZl1f57P/YUVHi+3nyoVN3PH6tzi9Vl98RY1NW78cryvSHd39X7SjyPL7zwVeO/6y8s9b6usCL0vwutwVnvwr++Nl6P604aFLLP76gWOvY44+xcnJtChxACVbomjkhzfP3116pETFwSETjiAuzdG9oPrKaQ8APZB999khI2ETKCJMNJU7304qxfv17t32efGWUl8gEiDpD0laxZs0Ydtwi3roi4byJEZRNE0MnX2n2TsrOrrrrKc/sbb7wRu3fvxi9/+UtVuiihDm+++SZuv/32TjsGQkLBYXPBLhSUh37do4WJUFrtxO6CBtegpYiw0Y9X57LhWJUTnUFuUaXHqRH2FlQ2K3ScLjfWHzjW6Db5ZdU4UFyl+mHuPWe0uu7Vb3NVkE6o0ELqrrNGIi7GjrX7S7Bqn3GdsDO/HJ9tzVf7kG06SUfLwmMNvHBzHg6WVCE9OQ4XTurbIc/Z5YVOjMfRCQ81SwjpXCQp7dtvv1WioKCgIKDbMmzYMLz77rtqwbxu3TpcccUV7eLMBELS1cR1kN4gCVB4+eWX8eSTTyqnSfjf//6n+n9k/yTsQMISZP/EuZHjEydKAgtksS/HcfToUSWeuiJynNKfpHuUpJdGvhaXS5DSQ2vJoURLS0miuDjigkky3gsvvMBoaRLxtLejo50PX3ehNRRW1KK23uV33zuS3UcrvL8PIN72+FyvBYe/12N4ZopayPdPT0JJZR3eWX0gJPt6rKpO9egIs0dn4YJJRsnaP5c2NPT/62vDzTljZBbGmQly7fG70Bpe+MrYzx9M7d8h0dJRIXTscUY5CkvXCCGCCAVxRKSkS+bgBOq5kTAA6WmRRDNJW5NF8HHHHddh+ynPJS7D66+/rlLiZNEugQniMglpaWlKwJx++ulKwMhsGCljGzNmjOo1+fLLL1VqnDhAEpsti/mWzJqJtCGwcnbYd3vppZfUz+VyyZIlje4jTpdERu/atcvzuhISyVgdlwKz/6U9Hl+cBGHN/uI2P5bn+9LOETpWp8bf977Xj+2b6ind80W/HscNSFORydeasc4vfr2nzWV+wrr9hpASAZXRLR4/OsmIEF+w8Qj2F1WiqKLWI6quP3kQMrrFhY3QEWG4OrdE/e78YJoRktMRdPkwgph4w9FJtNWizt1xZ2MJIeGJLPxljooVf4tccX50GZjm5ptv9vret5TNX4259My0ZLFu5eKLL1abP0466aRGi3eNCJ8FCxYE9byEkK6BLKSt4kEck1CjXZdThmeohLe2ODqHSrzdoc5ydLRTk5LgUIlgzQmdi4/rh40HN6tFu+/f7DXm6zGpvxH88r3JOXhs4XblGi3Zno/TR3rH2rcU7SId1z9NXY7MTsXJwzLw1Y4CvLRsL3okxaK6zqXE2NRB6WqWpFDYDmWMLeWfZt/QeRP7IDMltCEZUe3oOOItDcbOzvlPRAghhBDSnhRVepeCHW2XMAJDnMwb11tdbs8vQ2mA5v3m8HVwOtvROWV4L6/vrcgx6p6ncyf0gcNuw9GyGtWPo6mrd2H9QUPoHGcKHYl0vnxq/4ChAS1FxJV6/AENCZra1Xlj5X68vHyfV6N/Rrf4sHB0DhRX4uMNhz371pFEgdCxDHKro9AhhHQO0gAvJWX9+vVTl926dfNs8jNCCGkLh0u81zjtUbqmXZfx/bqjX49E1cSvy6la+1h2M3jrSGnnLMa1sDljZKbx/dGKRk7NXvM2mSnxSjyM7pPaqE9n65Ey5aZIGtrgjIaT7FdPH6jK2JbtKlRpbW1x7HS5nBZSwqnDeqmo5vIapxJfWanxOHuc0buTESZC5+Vle9Wg05OGZmBU75bPHWsLXV7oxMY6UOM2KvRctcFNUSWEkFAj/TUSBy29M3KpU8Jkk58R0hWRhdcVz3+D/1t3CF2J+z/chJv+s6rFfReSiHXxM8uwcNORdnNbdGJvS0vXvtx+FOc9uRSbDjVOExNkIS2lXUJ290TPYttf+Zrc9vqXv8NTn+8MvL9m6drwzG5+e3as/PubfbjkmWUoDnE5ntWpOXV4L/XaldU4GyXWaTE00BQw+tjXmA6L+nq/8bpNzEmDXas3AH3TEnHW2Gyv8q1AfLO7UL0H1hQ1za6j5er1T4i1Y0R2iud6ea4fzWhwSa6aNhBxDmN539Ps0QlF6dp/vtmHk/74GaY/sthr+8EL36LGGThVrqLGiddX7O8UNyc6hI7DjmoYb7SzhkKHENI5ZGZmYujQoWrAp1xaN/kZIV2Rd1cfUGeynzfTlroCsnCTfoiPNx7B3kL//RyBeHThNrWIbY/XQ5d+DTIX4y09i//6ylwVmfzBWv+i9IgppKSXRUqydJ+Iv0CC11fk4tMteXj6850B5+NoR2ei+ThNCZ1/Ld2D7/YVqz6XUGJ1anp2i1eixF/5mk5m007NJH3sFkdHYp593RbN9ScPVpcfrjuI/AAlevI6Pfh/m9V78PD8LY1+rkXV+H5piDXDIDQXHdcXOemJysG50iyVszo6R9vo6MhsoT9+vFWV6smQUuu2dGcB1pkizx+bD5cq8ShOk4jJjqbLCx1Jd6iC8UbX17TsDxIhhBBCWo8u7ZGFY2cNhAw11kVwU4tzXyQV6xPTyZHFbK0ztAFJh8zStXF9jUhhiTWWvpFg0Yv5QM34Wpj07p7g1XAvC3Crs2UdCllRW69cvaYeb1KOsb+HS6v9/o7IMcisG2PfQnvCWh+rFof6UgugQLfTYmaTZXCodnQkcc0XcXkmD+ihBoiKO+WP5bsLlSgQRAz7xlc3BBE0FlIS1Tz//52MxT8/FWlJxsl9oZcpdMQJCjTgNBje/O6AEitDeiXjw1tmeDYpYfSd3xTofR7QM9nL6eoourzQkYaxKrfxplPoEEIIIR2DLFp187QstNojBawzsAqBliSFyeJf64EapwtbzEVtqNCOi/RASE9IS0qWRKhodyqg0DGFVO/uiZ7niXfY1WyX3Zb7fLLJGAqpsf7M37DQCf0MYSD9LfJYvoiLIAM6m9q31qLFnRYw2rHZ3YzQkf6kXinxar82HipFaa2xn1L6JqLGH7psS0rA/ImOf5phBbrszLfMzTdxzZeUhFjVH2QlNdGB2Bjzd6GV///qXW7PbJ7rThqsHCW96derKcGvfy+1QO5ourzQkf/sNWbpWn1tYMVJCCGEkNAhi13r2fxQL1LDQ+hUBd0L8sZKY2aXnm3ibw5LW5AyIqFPWqKaPN+S8jUpexOhIewrrFCL2+YcHVmQ6zP6VvfhBcvwykDvu8x7kYQ4EQY5PRKR7HAHFI7WQZ2+Qzvby9GxPo+IMn27wb2Mn0uiWUPpXgn2lts8g0JFcPhjzphsVV5WXFmHd1cf9PrZ7qPlWLzVKMv726UTPbNxJK1M//7oQaHaSQsGm82GnsmGq1PYyvI16ScTESfR1VIiZ6W3WerXlODXTmM2hU77IG9ytc14kxlGQAghhHQM2s3RSJpVtDo6b67cr8q4hmV2ww9PHOj39WkrRyxCpKVpW9ZjkvKqg5bYZM/jl+oz88bi1rspv9hTciWlbNI2cM743gGFjn7dZD9FMPWIDywctesSKBEtlEJHhw1Y91mCCSRcQUyynPSGJN+G0r1j2Ftm8+rdCXTi/Zrpg/wOEJXvhVmjMnHWuN4qnUzEpqSVCWvVzB55fsNJagkZKW0bGqqdpR+cOECVyFnRorcpwa9/L/tYfm86ki4vdIQas0fHRUeHEEII6RBW+yRH+SthigahY+1ZkfKl480ZKL49GG3BWgpmCB29uA2uXMn3vdntxznRZ+atJUgNTfmGaHvRXBSfP7EPpgxKb1bo6MdKi2vK0Wm4f1M9Py3Fn1MzOMNIgNtbWOlxtfRt+vVIQrwjppHIkxACLXT89c9Y+f7kfkiJd6j0vS92HFXXSZLc26sOeM3E0WVuklYmIku/vs09vj8ytOgta3npmghYCYGQ8rcfnjig0c+zUxOa/X+gRRAdnXakho4OISREDBw4EI8//njQjvL777/f7vtESDiiz/LrngXfBu9IRBbHUmbUEqGzYNMRVcbXMzkOF0zqiwk53VXJlpQD5ZeFZr6ftRQsK7XB0Qm2XMn3vfH3XmkhZV2w6oX3trwybD1Sio83mkMhTx5kKQPzJ3S8+zbS4vzPAlL74pNsF6oSSEki83Vq+vZIVIt6CYo4ZPYZ6TI27fZopGxPDQ4tr8WeMgQMIrAiZW2XnpDj1ZPz3xW5qmxwdO9UTBvcU10n6WTS+C8BAOIGNhVE0Bw9zdK1goqaVrs5503oi0xT1FiRMsnmhQ4dnQ4TOm4KHUIIIaTdkWZrSaQSLj6+X5fp0ZH+ilJzloy10TqYxeKVZumPLHZHZKUEnEHTGvSwTUnZkujhBkenZaVraUmxzYqTPmkNC15Z/Eoks5RV3fnWOs9QyJHZqR6h46/np8HRMRa/afFNODpHm9+31qAf1+rUSHmZpINZn0e7XdYhoIK8l3pwqAs2pCY4PI5QU1wzY6ASVxLLvOHAMU952vUnD1InxzyzcUxXR8ra/A0KbXHpWlnLHB0R5xKh3tT8Gy165ffMX4qgJObpaGs6Ou1InRY6dSxdI4QQQtqbjQePqUQqcRZOGZahrttTWNHiAZvhhj67L4taLXyqagPH9lp7VqylP5OamEHTGnxLwRp6dIJb3OpF/cwRmX5L2WR2UKllWKiV48xSvI0HSz1ujj6DL/03/np+fN0h7ejoPiCNvLY6ZEHvW6iEjnaKfJ0aXydKCyJ9vRWr8JiY0z2o+GQRVmeNNfqXbvzPKuSX1ag5PueM7+N1u4sm9VMBAOL8lZqDQkf2bhgUGiy9WtivpREBJgJ1+pCeHkHni7iU8rstQjfPz3wguU5+Ji6Z3LYziAqhU2un0CGkXZG/ZLUV7b/VVTa+rgWNqc899xz69OkDl8v7zNP555+PH/3oR9i1a5f6OisrC926dcMJJ5yATz/9NGQv04YNG3DeeechOTkZPXv2xI9//GOUlzeUwSxZsgRTpkxRP09LS8OMGTOwb58xc2HdunWYOXMmUlJSkJqaiuOPPx7fffddyPaNkFBijcKVhZ2nHCjIlLJwRTfGj+vXHUlxMV6DOv2he1YumNTHq4nc08geMkfHWzjI8MtgF7fWOTUzR/oXE1pI6WGhVqxxx0Mzu+HUYcZQSFn0DzLdEd+eH10W1lzpmhYjEpusxWGoer0COTWDfYWOT2CBFWv4wEQzJjsYtBjUMdxXTx/oiZXWJMbF4MqpDeJ4fN/Gg0KDoWcL3T1BSvpeW5HrcZoCIQ6U/p3z9//AKmg7Y4aO4P3b2kWps5l2GYUOIe2DCJCHvc9GhRr58+73Y+SeQ0Bc4w8gf3zve9/Dz372M3z++ec444wz1HVFRUVYsGAB5s+fr0THvHnz8Pvf/x7x8fF45ZVXcO6552Lbtm3o379h2nRrqKiowFlnnYXJkyfj22+/RUFBAa6//nrccssteOmll+B0OnHBBRfghhtuwGuvvYba2lqsWLHCU8pw5ZVXYtKkSXjmmWcQExODtWvXIjbWf4wpIcEiEbZ//mQr/n75JIzpY0QFhwJdkiVn+3U5kDRgy6JRhE8wPP/lbvxvw2G8ePVkz8LdX6P/T19dreKU/3Dx+Bbt47KdBfjN+xsxLzP4BZh10SuLfxE+h0uq/C6CZTGve1Z0GZKvE7D+YIkSGr4L2Fe/3YdXlu3Di9eeoErDmuPIsRqvUrCWhBHIIFM5c58YG4MTzQABWYBL+aFO2fLtqbFijTv+0YxBXgvagRlJqn9HXrfTRlj2t9SndM0SRiB9UPrvnvX1bqrnJxASy3z1iytw4uCe+NWZI71+FsipsT6PvC77CiuDc3T6B///R+4nAlGS98SpuWKK/8+Xq6YNwD++3KVcsUnN9P8EIsPTrxV86do7qw6o2VcS0nDacEP8BkJEjAhlLV6taDeud2rn9OdEjaNTZ9dChz06hEQzPXr0UGLjv//9r+e6t99+GxkZGcotmTBhAn7yk59g7NixGDZsGB566CEMGTIEH374YZufW56zurpaCRV5/NNPPx1PPvkk/v3vfyMvLw+lpaU4duwYzjnnHPWco0aNwtVXX+0RWLm5uZg1axZGjhyp9k1Em+wvIa1FRMJD/9uMXUcrMH+DsSAP3aBQ756Cli5S5TGe/WIX1u0vUc38gZBhjQs35+H1lfv9Dpts6vEf+miLOqu/4mjwQkc7DIMyunmaqwM1Yq/cW6R6VibkpKmeFV/XQFwKaUL3HRxaVl2HR+ZvVQJBhGjbSteaP4uv3xMp4RLXSVLBxCjXLo/18X3L1gRpopfjkUWx75wVeZ18ww3ktffd3+6mo1NVV+/1PnpS0SxCJ9CcH398vjVflQ7K75JvwEIgp8b6uyqLdwl5ELdFN95bkcGhk3K6Iz3ejeMCDAoNxK2zhqteHRGHPQKUdUkP1NXTjJ4eXe7WUjJaUbr2+TZjpo8IsOacmD7a0fHz/8AzLNTS19XRRIWj44wx3mSbk44OIe1CbJLhrLQjUm5WWlaG1JQU2O127+duAeKMiGvy9NNPK9fm1VdfxWWXXaYeUxyd+++/Hx999BEOHz6sXJaqqiolMtrKli1blDCRsjSNlKbJcYljdMopp+Caa67B3LlzMXv2bCVqvv/976N3b+PD7Y477lAOkAgj+ZkIHRFEhLQWnQYWKO2qtchZXOk7kESqcX27e0+cD3KWzv6iKs8kd1moWkt4rFiHbspCVkRFMCzfVegRGEergxc6ev/leLY0UbJj7I8hFEZkNW5Ql8WjlD0t2XZUxXDLlHnNGyuNSGF9TMHgcUjMxbguk5M0NumLamqxahUT4qQM6pWM9QeOqWMdboYmNMxCabxgFRGw8PZTUO92e8Uv68f0LTdTCXFm47okxMFdD6kClH4U6XkSEZSWFOf1eov40D0/cl/p+enfs/m//TqWWYTbv77egwfOH6u+b8qp0d/LsM5tR4w4tYE9k5Qz6Yu8Xv+97gR8/PECJPuU9DWHJKutv38uks0SyED8+uxR+PmcEaqUrS1Cp6iyVp3ccDRT/iZCVL9uOiK8KbKbEPwNArnzhE5UODpO09GxOUP3h5wQYkHKDKR8rL03ETW+15klDsEipWjyh1zEzP79+/HVV18p8SPceeedeO+99/Dwww+r66U8bNy4caqMrCP417/+heXLl2P69Ol44403MHz4cHzzzTfqZyLANm3ahLPPPhufffYZRo8erfaVkNai08BaMviyJfNzRvVO9SzO/A1ibPIxLAKmqXkz1qGbLSlpsh770SDPgYpgaHB0ZOFtrC38lewY+1Pu5Wr4MinH7NPZ33AMshB9yUzhaskx+TokUsqnF/QlzThdvs6GP/etuVkosnj2FTnqscz5NP5mD+lhob4zWazOgOc17JXcZM9PIKxC+K1VB3Cs0ngtmnJqRCSK+BDT6IvtRwOWrVmPvRWtMwrpd9JleoGQn7dW5AgiIOUpROyJkGwOEaXiqklJnfwfbg6dwudvaKg+gdLbTzR1R2GPJkfHTqFDSNSTkJCAiy66SDk50gszYsQIHHfccepnX3/9tXJVLrzwQiVwsrOzsXdvw6KjLUgpmgQKSK+ORp5PnCTZB4304dx9991YtmyZKnGzltmJ8Ln99tuxcOFCdQwijAhpDToNTNPUZPO2BBFo9ELRdyZKc4+hz+qXVNY2O5Q02Cb1XUfLsXhrvuccSbnTFlTZm7gmUmomTpWULOkz2f5KdpprYrfOXLEeq5ThScqW3rdghI4sYD1hBOaCUnp+dBxzcyVLgYSO1U1q7SwU/Vi658frsXzKmbRIswZWyODO5vYtmIhzeezK2nq8tjLX63fFn1OjXS3hs635fpPZIglHjB3ppkMWTPma/j8VbPiBP4GqOezjNHYGUSF06u3GC8zSNUKIIA6OODovvviix80RpPfl3XffVU6OiJIrrriiUUJbW55TRNZPf/pTbNy4UQUiSDDCD3/4Q5XytmfPHiVwxNGRpDURMzt27FACScrnJLRAUtnkZyKQVq5cqX5GSGv459Ld6vLEweleTeChQLssOnbYWsIkje/+5m00fgxvF8cqyjT5pdWe0ruWuB86Ce2MkVkq1te6oG4K/fhSMiWLx4aFebX/waK6JMxcNPsig1RF0EiZ3tGyGi+nSTenWwVCICqckpwmDfxmKZhvb4b52M0d18AmHB1/w0KDQSKFJanN2vOj+zb0AlmT3T3e67lE3EqZm9o308lpiTO4wYw4F4fm9tnD1XUvfb1XhT9ooaQf1xftwunfL99ktkgjowV9Ovr/rzVRril0oMThpnp0WLrWvjhjjBfYTqFDCAFUEEB6errqjRExo3nsscdUYIGUjkmJm/TLaLenrSQlJeHjjz9GcXExpk6diksuuUQlv0kggf751q1bcfHFFyvnRqKnb775ZhWOIClrhYWFuOqqq9TPpHdHQhUeeOCBkOwbiS5EbOgm91/PG60ua5wulARR1tIcsijffOhYo0QqazmQtcndH5W1Tmw5bPRGzBjaM2D5mu91usypKYoravHO6gOe2Fw5ox+sQ+BxPszFsW6w9jc0VPqLJLVKxEf/dP+9JDI4dHimOTg0Vxy2YuW0yVySW88YpsIKgnHBis21q28pmJ5bogc2Bnqt9QJVL+b10EurQ+YbBx0syh3x6c/yJHH5PJYWPofMcif9esv1uv/FX89Pc86EOIvnT+yjXh9xviR4w/NeBhChjft2mh8EGs70bEHEtC73s6bpNYX+fyC/Z9aTGCIopVdP3aaFTmAoiYowgnpT6MTUs3SNECKNwHYcOtQ4PGHgwIGq/8WKiA0rLSll8z1DLuVwkuAmc3C8AhWkKTcrK2DPTVxcnCqzIyQUSA+ICI6Th2WoeTCyIJaFuZQMBUp/CpZNh44pd0EWlVLe5VsOJEMlZZEp81YCIdPipbdEFrjzxvXG1zsL/To61obpFXuKVFywNZrYH/9dkavKz8b0ScXUQekYlJGEFXuLsacFjo5eBOvIXOl7sEYxW28r0dDW6/2Vr0m6mggdPVTz3Al9VNqWuBeSOifH5ZvaZqWk1uY3KCAjpflYYR2YIGVu+r2XSGi9KJZ45hibzTMstDUlSPJ6SbiBFmzasfF9LC189NBQf6V//np+AqF/P2TBLv1DEtX82KLtyjXTIjKQU+N7fVM9OpFARpAR0yLO5ffRWlrZHFIWJ+Jcep7yy6o98fEicjp7WGjUODpuh/GfiUKHEEJINCPRxZLqJVxnznZpcCaqQzY/R8pefAWHPivenPNiLZ3RDftr95c0ihTWjs4FE/uq+N2K2npPCZg/5GyzTHvXbo7s3wDt6LRE6JiL7dREh2doqG/ZTqAZLb7o41u0KQ8fmy6bfl+CdS9KzLWrb1lZryDKlfyJCXGa9MJYnC7PsND4xsNCg8FTCme+JoFm8mhHRz+fb0md9bGaK+nzF3F+5dT+yvES0fXtniLz8fwLbq/XI97hmUsUqWSY72dT7p4u9xNxIicpMlOCc+8kJCLLLDu0/j+Q+VKClFN21rBQtX+IAlx0dAghIUbCDLp16+Z3GzNmTGfvHiF+0dHFaoL9cGOCfbbpTIQiec13cWkl2Fk61scYkZ2iSt5kn3fkG2eatWiRBaswdXC65yxyU4/9v/WH1Flm6cs5e1wfrzK0YEISfEWBdSq8b5iDFifNCR191lxuL0Ju+pCeGN0ntUWvV0mNzW95UMPQ0KaETrnf/Rxsee4GB6Z1fRa+x9GQEOff0ZGkLq8eJ8u++ev58YcIIR1xPr6fEXEuQ2cvNuf86BKrQO+Pl7jqZcRuRzIZKebvQlnTjs6a/cdaVLbWVJ9OawMsQk1UlK65TEfHQaFDCAkR5513nuq18UdsrFEWQUg4YY0uFtdAL948C8w2Jq95n0VvXPYiZWLNzdIxZniYjzEgTSViyWycZbsKlVukS7hkBo70FUkJkh4mKQtfWUxPHdzT7+PqRv+rpw/09LI09OhUNln2Jv0GemGte1j0ayfH4zuHaG+QQkceKzXB4SkNE6ep4fUKLmFMOzq+Doks7JsrV9pjlq75K9VasbdIHZsWBf6GhQaDtefH37BQTVZqvGdoaGmV068rJu+P7Os6nzk/gVxBEY3W0kEZzvnaiv3NOjXyeyU/KyivjfiyNSEj2fxdqGja0dFR557/v7WVQOEOoGAHcHQbUHEUSOkNdO8HdO8LdM8BUvs2lB1a/oa0NsAi1ESF0HHHGi+yw0WhQwgJDSkpKWojJFL4dEu+ii6WGSsXTmqYYK/P1LfV0ZH755Xqs+j+hI4uXQu8cJcEMllcSl3/mD7dPc6OEjq5xbhiqpFGpgWVLpGTxajMPAn02Cv3FquoYZkNIiVMmpz0JNjg9pS9SW+M//2qVI5LYmyMZ0FuPZPtOzS0uWhp78GhPdS+SzrbacMzLa9XkI6O2aPju6AMJmlLOzq+8cnWXhgdv9zaWSjWnh8Ri1o4ZVpeR0EEiR4aKo6MZ2aRT2CA6l06cKzJ16UhiMDbmRiWlaKcTHm95XGacmokkU1+FwMls0Wko1Me4HehsgjpZdswNHcpTnbsx8VbKoEVe4BjwQ3LftjRA9fFpSF2VQ5QMRqI64Yx2w/ifkchJhQlAu8mAvU1gNPc6msBGfmSPR445zG0J9EhdBzGf7JYETrid0a4BUlIuBCqOFrSufB9jA7WHTDO1p41NtvrLHfD2di2CR0tPkb2TvE74FCXiUlJUUWN0+8k+TX7jccQkaP3UZd3WYc/6kZzvZDVEc6B+lm+2G7MQ5k3tjfSzJkiQrzDjvR4oLDGuG8goaMX3b6LY39umAwW3WPe3ur+BOKi4/pi+a5C/GLOCK9eBi10JChCBl12N+fi+FJirl19B182lK415ej4F2TWuUf6fWpt6Zru+ZFFtghWY9/i/Q4YFeEoQmf9gRI190ZEVo5Zltho3/R7La5D+RGgTLbDQFUxBm7biDscxTi7JAX4wA3UlAO15erymfIS5Mcfg9M+Blh/GBg2G0hsLMxnj85SPSunmCWebaKuGqgpA+oqjK/rKo2Ffl1Vw6X+ur4OSEgFEnsAienmpbk5WtcrlOGJGvf5XcjfCnzxRzg2vYeT4cbJcp283UYwoYHsQ68RQMZwoFuW8RqXHgSOHTC2ukokO4sx3l4MHNsDfPulutt02eSxZOaqMXe1Mfb2r36ICqED09FRiJK0fk8IaTG6NKuyshKJiZ1bf0vajryPAkvuuja6OVhcjGDnYLQmiMBff44gC3Wd8CYL7LF9u/t5jAanRjPRbNjfZQ4OFaHi2wvUnPuh9+2EQcbcICuZiW4U1tjUfU/0U/ZmLbfzLfHyvHaW0jVJrxPXQlypvpbkuUCcP7EvzpvQp5G7IAJD+olEGIpwmpiU5vckhSeMwEekWRvQ/ZXlSdS2iArB17Xw9OgcrVAOoDrW1DjgyEYgdzlQng90yzS3rIbLuMApZiJ0lu8sQCKqMa5bLXB4vRIltooi9CleCdumalwQswfD7cdQt3oDLokpQq/kWMStLwDcLkMIlB3Bxbm7MTl2FwZuKQP+cAyoNvpKrFyjV7jGuCgv5Ld/oLwURw8D734K2B3AwJOAEWcDI+cZZVkAfnLqEPzopEH+h2bKySFZ7Odvgf3wBow7sAwxH35kCBnZn5pSoLrUEDfytTgYoSCumyl+0oAeA4AxFwEj5jW7ru2pyxgrzN8FKUX74o/AxnfkYCAvR1FMBtbX9kV5ymCcc8ZphrDJGAEk+/8/4XkdqoqxbM06/POjrzClRyV+MjFOibl3NxTgQFk9zpwwEMP79AQc8cYWI5dxgCMBSA6BiGyGqBA6NrNHRyEqmkKHkDYhc13S0tKQn5/vmQHT3s2aMriztrYW1dXVjaKZI4VwOwb5wBORI++jvJ/yvpKuS6DeCKsr0Vw8c2uDCDQiSJoUOj5OjSALbbmf3Ed6CCQaWkrwZDcn5HT3Ejr7Co2mfuu0e+lN0m6WvyGIvRKALc30wgRyPhpeu+pGtx3QM9lrP5oi0Gsuz6eETkG5GjDqS1FlHZxuW6NhoVahI6JLwhzEWfE6psLGc2o0MhQ13laH4bXbcPz+nbgqdhNOWbQLqGsIhAi4ENeiRxaxIlCqivG34sOwxxchbXsF4hPqAHk7/mHcRZ75BPliL/Bj9RhiLwI/lN0VffCh91PkyCZ/qiR0TQevyTovtTfQLRvFSMFneypR70jG96aPgi2+GxCfYuybfC2XNjuw50tg23zg6FZg9xJj+/gXQO8Jpug5G7FZY1RZF/I3K1GDvE3GpWw1hsCSXRmMJlwLK7KfseYmC33fr+UyJtYQSVXFQFWReSkvmNtwpWSTkrIj64Et/wfEdwfGXABMuBzof6LfqqWepljt6zqEurduQNyWd4z3Rhh5DupOuhO3vHUAy/Ls+PHYwTjn+CCHUctzJaUjZcBxWOyqxIbaePxk9iz1oz+u/RR5zhqcNm0G4KeUtaOICqETExuLWncM4mz1xhkBQkibyc7OVpda7LQ3sgCrqqpSDlKkJuCE6zGIyNHvJ+m66D4S37QrvUCW+TIyNLQ1s3Qk6ldm6AQjdL7bV+zXeamqrVchA+oxBng/hggUJXT2FaOmzligjchK8SzeJdlJAgZkUS/zaGShrtmeV67KoCQaeZg5oNNKrwR3szHOgYSOv9S1YPtzgkFK8iQKWTfm+6LLDTOS47yGhQpSPiiJddJ/JOVrjYSOb7O/uA/7vwX2LUd87nKsj1+JeFEaLnM1L+aPiIR+JwDpg4CKAsPZKc8zNjmRLIvwItm8rZTe8o/lT57T5oBDnILEHnDFd0dhSSl6ZmTgYEmtmmnkgl2W9eibnozhWd0NYSIuQLds1CT2wq8WFiAfaXjup+egW0Y/ID7Vs8B/68tdeHj7VswZloXvz54c+MUdMhOY9VugcBew9SND9OR+AxxeZ2xLHgbiUoDaAOJOnKCew+DqNQI7C+sxZPQkxIjrltDd2B8RV1KCJl/Lpbx29laeTHK5gOoSU/QUG+JL3qv1bwDH9gOrXza2HgMNwTP+UuM9Mkko24e/JfwD57i/RMxms1RZnKDT7jKEXV0d9pYdDBgk0hz6/4G4hxLcIehhoQwj6ADEdqxGPOJQSaFDSIiQhXrv3r2RmZmJurq2T1RvDnmOL7/8EqecckrElliF4zHIftDJ6fo0lXYlvTC6pExu0xqhI43+xqDQOOSkBy7Xamrgo/RlOF1u1ezvO/xSxNO7qw8qx0fS1nzdGeltkQQ1ETW7C8q9hI52msQR8eewZJq721Rzu+8MHY2OzrUODdVlbqEQOvoxAomw5pKtZGhoRWGlKhvz3Z+8Q7k4074CP6w9CPzjl8CRDQ1n+aV/SXo63KlY6RqBla6R+PkNVyM5ZxIQE2DpKH0wSvRo8ZMvb4wSMyvzbbh/0SGUuLspx+VncyfgpplD1d3q6+qwbP58zJs3DyvW5+Hnb63zPORDJ47B8GkDvZ5G9uvrrz9V4RG70RfjRVj4K6H0EcsB6TkEmPH/jK38KLB9gSF6dn3WIHLSBgCZo4HMUYC4PHLZc6gqxZL93zJ/PgZNn6dOrLcL8jompRubZvgcYOavgX1LgXWvA5s/AIr3AkseMbb+04BxlwAH1wDrXsP5Yn/ZgOK+M9Fj3n1A3+M8DyWO32EzrbupExWBkL8femhoXmm1Wh/oYaE68a2ziAqh44ixoQpxSFVCp/mhYISQ4JFFckcslOU5nE4nEhISwkYkROMxkMikqKLWk3blW+KkF8oidGQqvZ7j0hJ0UIAkiDXlVupAAn8Ldx1tK0M0fR9DixoZHCrxw/q5vB47I1kJHVWCNqLh+qYir62Ojr+yN+00aZHo26MjQ0MliU32SUSHhBWE0tHRSXWB5vxol863P8e6AN1XWInCsmqgaI/RX7Nvmbr8aeFOo0yswHIHWdAPmK4WyX/fmYHH1shrY1NRzPcNVAVmgVElYt0M4eBDWl4ZNi00mtSFPgF6l3wDD5oa6ClCR15ra8KfNeJ8kp9Sv2bp1gs47ofGVlthvGbikshxhSMigAadYmzz/mw4U+teA3Z9brzXucs9N10dNxkPlJ2HH0/9Ps7uqzw2DzKTSjp1+qYlBAzkCGZoqKQmyv8D/d+3s4eFRpej444zbFNJtCCEEEKiCL1Ql74N3xInXc4mrswhn3kwwWKNe24Kj6NztLxRP5AnEthMWbMiZWpJ5uDQVQGig41FcV4jZ0YntAUagtgjHgHL3qwiIy0p1iuxzeNspxmzdCSEQISOJxa5ZyJQlgeUHQJKLZukgzmrjHQttUl5mNO41Ne5ZHPi1HoXFsdVwS79+E8kW6u/FOdW1OKUuFp0PxgDPO4wmsPFlTG3f1XVoDa+Ht0leczpXYLlgg3bXP3QbdjJyJl4hiFwUo1BqkJq9R5gzeaQlB+pGG+bsXvq8QIspn3LKn0dNM/1PZOxYo8x58fKoWPVlkGhbewLkWCF7LGIGGR/x3/f2OT3bP2bwJYPgeRM4OSf4/kv7Fi38YjfiOm15qDQiW14zXqnJiqhI39r9H9rX/e4M4gaoVOlzE4zjIAQQgiJIjxTygNEBLc1Yrq5xDWNTveSAZniMuk0KONMfODHcMTYMaFfGpbvNuKJ9aBQK/p7q1skyWJa+AQSYXLCeUB6InbkVzQqe5Ny9/zd63GifTPGpMQCm80YYEss8C3YiaOOEmR99X+oX1WNR8s2ITu+CH1fPWYIljYgsmqI1qXGoXsheVg97WbTvp9gL1XUpU7ymlG+fSYBA6bB3X8apv2nAnl1iVh85qlAr8aOxSDLdW0VOlLS1zctUYVI+IvC1lgXxhL9HWh2T6ASSC2WR/VO9RtxHjWIYD3pNmMzyei2UV36EzraTZ3Yv3FASLA0zOOqgs2U5L7CtTOICqETF2NDtfpzYfzRIoQQQqIJPbE80Jn0hqb6lgsduY+UUEnJ1/h+3YNa8OqBkFroyAJYFmBS0+8vjU07PVroiGjxLYnxt/jVc3mkqd/XjVG46pFYW4A5SfmYGLMdad8sATaWACX7gOJ9aj7LqQBOlbvKSe83Gz/ERXo1tdf4frIWJlIpKE30kkAmC0/ZUmTLNhrTpdclJs4QIDF6k+/19bJQt+Hm/65WfQ8PnDfGM0RVc9+HG7D5UBluOX0YThvVx2jIl+eUDTa88k0u/rPiAOaN74PbLp4FxBkiLk+Gu9Yt9junRmMVkroXqS1IuZkWOr7DQv0NDZXbByp7ss75sdJcmWI009MzV8lb6MhJBhnA2upyPz9/QxqEDh2dDsFht6PKTUeHEEJIZCGLEOkbEUejKSRCWRatgfpjpKSnqYWHdnqs6WH+9sXf40vfjDCqt5SXNb+skEWqCB0plRtiugbLdhmNIqMtg0J90U6PDS5M7tfNGBTprjfKvlz1GJxQiWyxPUpcqMnbgXhnKUrWrsX3YrbitEQb8Mkio0G+4qiRGFZxFI7KAsxxOTFHHlja5vY0ft4qezIOONPQPTUVmelpZhRwkjGqwpGItUeqseJAFYb1zUCf3n3x+IoKJGXk4NHr5hkiJ1DzfpCUZ8Xgu2NHsT5mNMYM6O/1syVVVch1VyFO+mf6ZTa+c3YqtrttGOwUcdUgaMS5EnJ6GGl1/hDXRTeYhyI5S4TTVzsKVGCFv2Ghnl02h4Y21ePkce+OVijXTv9aesoagw0iiCIy9NBQnwGycmJAXm+HzY1R2Y1TCYOlj2WmFEvXOphYM4xAQUeHEEJIhHDHm+uwdGcBFt1+in9HwkxMmvXoF8pNee4q/3G6uiStd4CSoezUxCZL1276zyrVsPzBLTM8CybNGrO+X0IEgkEWsHJM932wSW1W1BllidKV6evFe4xmcPPy1ILdWBu/A2m2CuBrid5qXMb1jV5XPdPgtlwkAkZS8P0k4ct6zGWLQXliX6wt64661P44Y9oUYxijNOb3GIjL/rkJ6w6W4qk5x+Hs8d5N3MKmb/fh4b0bMSspE1PS0/GxayvO7d0H6N43qNcjmNfri+1HG5VpSYzvkdKaJheUDYtb77P4O/MNodOUmBDhPKBnEnbklwcseWzpcRj72rQ7JIl7EjPe1L5JeaEspuV3f9JDixr9PNjfxWgiI8Dvwnd7DXGYIyOGAojeFjk6krrmuS7CStceeeQRvPvuu9i6dauaAzF9+nT88Y9/xIgRlngTP7z11lu49957sXfvXgwbNkzdR2IEOzZemkKHEEJIZCELXOll2XyoFNOHZvi9zbYjpap07OhWY4aFv0nuh0qqgnR0qhs5N8cq6/DxxiPq61eW78Mds4f7re/3FyLgjzljsvDO6gOoqa3FYNthjLLlYqQ9F6NiDuLEXceAhw/4DQ6SBUtaUwFONjvq3HY43XYjNj2pBzYdi8NRVwomjRqGtIw+xhBL2SRdK7kX6uJ6YP5Xq9BrzAxc9c+V6OdKxBknne55yG1HypTIkQqqiQHKofRrKkEOoUxc00jZneDbeD9/w2EVoJAS60Z2gFIwf4tbeX/fXnVAfX18M87H+RP74OXl+zBtsP/fvZZw6ohMZHy+C2eNa3pm19yx2ep36oxRWQFvI47Q2eN643/rDzf62UlDM5qMOI9WeqX4L13774pcdTmye0O0eGto6POz9uhEmKPzxRdf4Oabb8YJJ5ygIlLvuecezJkzB5s3b0Zysv//1MuWLcPll1+uRNI555yD//73v7jggguwevVqjB3bMWkWsQ47yil0CCGERBAulxsllbWeQXyBOFpm3EZK3PYXVWKwn8byQMNCNTpyWmKSj1XVeblHaw8YQkb4zzf78NPThnjKyySxWkrQmg0ikPkkeRvVZPmT8zZhU5+NaiK9TZLGrBjmEGCLAdL6G0MPewzyvuyWbfSvSC+L2mKM29vtuPP1Nfhg7SHcdfpInDq8F87521dqUOi6y+YYqQO+yAwwmx2DMoyyLimp0/NwhBeXGrVsc8dkq94if+jXVF7jlKPGsko/XijQommPWW6mxco/zX07OdsVsLRR92UUWsqVZGCruHNy9v7yKd6lcL5I78/NM4eGZMCxHMfKX5/R7GN9f3IOvnd8v2Zv9+QVx+HxSxsvzpsr84xWeprzbKy/C1LqJ6Wn8rswPUsSK1qP/n8gqXe2AHHhYS90FixY4PX9Sy+9pIYFrlq1Sg3A88ff/vY3nHnmmfjFL36hvn/ooYewaNEiPPnkk3j22WfREcTabaj29OhQ6BBCCAl/yqqdcJlxvNbFiS+FFQ0iSBwFX6HT1LBQjSzs05PjlHskt7UKHZ1kJcjP31tz0LNA3l8hJVRuNa+lf7rP4r6qBPjun8DKF4FSw0HQeJaw0pQvAxjVEMbRQPpgQ8x0zzGa81srCo5WICXBWOJMyOnud1CoFdl/mRVTVuNEblElhmelqDPf7601psVfd1LDlHlf9Gsqr822vLIm57+0Bp1UJ/ul5/xYxcqMJhao2tGR49IC7oWvdqvrLprU1xMG0RShEDktfaxgb0dREzwZKcZ7XVlbj8pap+qn++dS43fhvPG9kRq3r02PL/+HpFVE/h7In61wGBba5h6dY8eMUy/p6ZZJrT4sX74cd9xxh9d1c+fOxfvvvx/wPjU1NWrTlJaWeqaKt2YCu83t8vTo1NeUw9UBU9xDiT7mjpg+315E+jFE+v53hWOI9P2P1GOIpH3tahSbbk6gSFjPz0xHR/Dt4/AdFhoo7Uov2A2hU6XieX2TrIZldlP9GuIkXHZCjrpub5mt8aBQmRPzzdOGwNGT5UXaiIDJGmtupriRPhgZehhy96MCTlMlBjPpXfZdUttEPMh9ReiIeyWv24SctCZLvCTqWg8NLams8xqMGgpUKIDPnB8tVi6c2BvdYgMvUFMTHJ5AAfkdEqG0cHOe+tmPmhBvpOuRHBeDhFg7qutc6m+GzVaLBWZJ6rXT+2PnqrYJHUnIkz4dmaUTLsNC2yR0XC4XbrvtNsyYMaPJErQjR44gK8u7zlK+l+sDIWVuDzzwQKPrFy5ciKSkltvBu0vlQI0/7vt2bsGG6vmIRMQJi3Qi/Rgiff+7wjFE+v5H2jFUVjKpMuyFjuVn1hky/oaFNpV2JUJHytCsEdNSPqdT1R48fyxueOU71cguvUMzBvfA3nItdNKAwl3Asr8Da/9rDL8Ueo0CTrodGHl2h0yXH2w6KfI66NeluSGmVpGkhY64HyJ0tJvTlMOghoZ2T/C89nJmu3tSy92oQIiDM7BnErbnlXvS0rRYuXraAOz4bl+T+ybla/KeStrW+2sOqqGdpwzvpcQciR5s8ruQHK/KM6UUVnq85FzAycMy1O/CzhA8hx4aGi79OW0SOtKrs3HjRixdujS0ewTg7rvv9nKBxNHJyclR/UCpqQ1nmYJl9d5CfLn1f+rrgX2zkNOBQQihOqMqC6PZs2erBstIJNKPIdL3vyscQ6Tvf6Qeg3bUScej3QF/kbCBStf2NiF0mlt4eHpNLEJn59FyVUInZ4JPGNhD9U+8+PUe5eoooVNmwxjbHly691Xgy48Bt9kzkTMVOOkOYJj0xnRcedFAszdGRI5H6LQgDU6XvX249pB6zSUB7KyxTTfPC9kWoRPKIALrvonQEREmIlPEivQgKZetmfuKwJXfAenxefO7/eq66+nmRG352sGSKuwrrMAbK/c3W5bZUqwx5OGQuNZqoXPLLbfgf//7H7788kv069evydtmZ2cjL88486CR7+X6QMTHx6vNF1kYtGZxkBAf60lds9fXwB4hC4xQHX84EenHEOn73xWOIdL3P9KOIVL2s6s7OoVtKF3Tw0KbEzrZlvQwzRqzbG18vzTVD3Ht1N5YtGwFKnZux7bF6/F398s4JX4DoE2FYXMNB2fANHQGKQmx6JUSj6NlNZ55Kz2S/cdy+6IFirgmetDoNTMG+k2x88Ua8tA+QkecqjzlOC3cZFTEXH9ycAtUmVsjPLNkl+rPGJ7VTZ3FJ9FHL/N34dkvdqlobhHKIpglYCwUWMMH5CRBxAkdaWj82c9+hvfeew9LlizBoEHN/yebNm0aFi9erMrcNHJGU67vKOSPVJVZusYwAkIIIZGADPELxtGxlq7JmXvdaGy9LjhHx/h5vcyuWbFCzbIZsmEzXok9gJEllcAfi5BTVYyv9HnIb6SuCqiHHTHjLgZm3AZkd0yaalOI0NBCR3qHWlr2JklUUtKTFBeDS09oOpVMY31tpdcn1OgBmR+sPaj2bURWiopRDmaBqgMJxBEKphSPdF0yfH4XfhTi34XeZnqjEIohsx0udKRcTeKhP/jgA6SkpHj6bLp3767m6ghXXXUV+vbtq/pshFtvvRWnnnoqHn30UZx99tl4/fXX8d133+G5555DRyGNeFVuHS/NenNCCCHhj46WFqSm3ne+jfVnVvYWVGJ0n9RGQqe5UpJ+iU7c5XgNPzryMTDfWECr8aPS1mM5R+iKicOBuu7IQw9sdA1CwZhr8YuLz0K4IEEAK/YUtag/x1r2ppPupExPggZaeiY7lEEEDfuW7LVvLREr1mQ16R86f2JoBpmSyKOn6egIkrJ44aTQ/i5YBxI3Nxg2LIXOM88Yo4ZPO+00r+v/9a9/4ZprrlFf5+bmwm6px5WhoiKOfvOb36i5OzIwVBLXOmqGjuCIsdHRIYQQErGla5K4JaUmUpplpcZZr3po9Fl/6ROR8jVvoWN87gWcbu+qB9b8G8d/+hCmOArUVe6cE1Hbayz+9m0Z8tw9cN8Vp6N7rxwgJRu2hDT87JnlWGeGFPx56GiEE1ZHJZjENY28tnLGWxwy0RDXzhgY9H3b29GxlsNJKdp5E/sEfV9duib84MQBnhlBJHodHeEHU/uH/HfB+v8gIsMI5GxSc0hJmy/f+9731NZZSOlaNcwPBwodQgghEVa6psvXfIWOnq/jsNswMSfNFDoNgyW9HB1LWYmH3V8An9yjhnnKkmeXqzd+5/wBHr/sl1h38BieXrZCzcfpPnam5y42s5n9Z6+tUd9PygneNekItCiQON0R2S1LFhOxKEJnzugsDGiBM5Odmtho7k0oEbGi5/y0VKxIz5IgEdVyXxK9ZJhCRyqdfjAt9L8L2ZEudCIVGVpUbTo67rqqhkFlhBBCSASUrgmyAPdtdNf9OVKSMth0EqwR09ZhoTKPxYNEQS+8F9j2kfF9QnfgtLtx+cIByK9143BZtWd+jr/yL0kimzkiAyUF+eifHh4lKprpQ3qquTfScN/coFBfLpuSg2NVdbhj9ogW3W9YVjdMGZSuYqDbwzGRMrWrpw/Esl0FuHpa8E6TIL08Mhfpgol9PKKHRCcnDc3A6N6pOGdCb2SmhF6I9OoWr04SuNzusPldixKhwx4dQgghkUVxhY+jYzbY+3N05EytkczlHTEtrpDXsNCqEuDLPwPf/gNw1QG2GOCE64HT7gKS0tFrxVfIryzF4RIROiUBy78kge25HxyH+fPnh11ju7he79w0vVX3vei4fmprzTrjzZ+0b8jSnXNFfLVMgOkenY9vPbld9olEFj2S4zC/HX8X5G/Bc1epzr6wITqEjr2hR4eODiGEkEhydKTk7EhpNQoqagMGERhCx5wDYxE6h0qMcu2hyVWI//pR4NtngcpC44dDZwNzfw/0GtFoaKjM2tDR0i3pcyGEkHAiKoSOnHmqMufo2NijQwghJAIoqarzlEUpoePH0SmwCB2dGiYuTnFFrTp7W5a7Hn9wPIeL6r8GPjcdoowRhsAZNjtgjf3XOws8g0JH9m5ZnwshhIQLUSF0pEa31m02cDqrpGhZ/LXO3i1CCCHEL5KmJsMdhaGZ3fDVjgKveTmNS9fi1OwccX/ySitRuPZ/6LHrFUzb/Tmm6U/63hOBaTcDYy4EYvzHJutI2M+35XsGhQYzMJMQQsKRqBA6Qq3dKF2zuV1AfS3gCI8mKUIIIcSXEjNxTU7U6ZI0f0LH6uigthI3JH6OU6vfwdBFh9T1LtixoH4yDo+8BtddcUWzJ/l0UlJ1navFc2gIISTciBqhU2dryJFXgQQUOoQQQsJ8hk5aojHbxereNBY6bkw78grw15dwXVUxYAdqYpIRP+VaPJx/El7Y5MKv+o4MqpLBd8gf+3MIIZFM1PjRbpsDdW4z8pF9OoQQQiIgca17UoPQ8evolNXiNPs6jN38V6CqGKWJ/XB/3VW4e9Cbqg9nY1Vai2Za+N6OQocQEslEjdBx2OAJJKDQIYQQEgmJaz2S4jyT7WVgqC+FFTW4OuYT45vjr8HKcxbhpfozsbXQGPB9xJyhE6zQsQ78y0lPDJtZGIQQ0hqiRuhIL2UNhQ4hhJAIQJLThB5JsWoOilBe40R1nRFQINS73OheuQ8zY9bBLYMTZtyKQZmpnohpl6thWKhvSVogZNhlerLxWUk3hxAS6USP0BFHxzM0lEKHEEJIBPToJMUhNcGBODP5zFq+VlRRix/YF6qv3cPmAOmDkZOepAIMqurqsfVIGWrMYaFZ3YN3ZiS5TZiUwyACQkhkE11CxxwaqsIICCGEdCmeeuopDBw4EAkJCZg6dSpWrFjR5O0ff/xxjBgxAomJicjJycHtt9+O6mrDAQmf0rVYNW3cX/lacXEBLon5Un1tP/FGdSlR0Dk9DPdm2a4CdSn3jXeYPapB8IMTB2BiThrmje8dwiMihJCOJ2qEjsPOHh1CCOmqvPHGG7jjjjvw29/+FqtXr8aECRMwd+5c5Ocb82B8+e9//4u77rpL3X7Lli345z//qR7jnnvuQTiVromjI2SYvTLWoaEx619Hiq0KufZ+wOCZnut1HPXyXYWN+m6C4Yqp/fH+zTOQmdKy+xFCSLgRVY5OtXZ0ZGgoIYSQLsNjjz2GG264Addeey1Gjx6NZ599FklJSXjxxRf93n7ZsmWYMWMGrrjiCuUCzZkzB5dffnmzLlBnhBEIPc2+GQkfULhcyNzyivry05QLvKKjB2V0U5ff7ilqUX8OIYR0NRxRJXTYo0MIIV2O2tparFq1CnfffbfnOrvdjlmzZmH58uV+7zN9+nT85z//UcJmypQp2L17N+bPn48f/vCHfm9fU1OjNk1paam6rKurU1tL0fcJdF/pvxFS4u3qNunJser7vGNV6nvbrsVIqdiLUnciNmac5fU4/dMTPOEFQlZKXKv2sa3HEAlE+jFE+v53hWOI9P2P1GMIdl+jSOi4LaVr7NEhhJCuQkFBAerr65GVleV1vXy/detWv/cRJ0fud9JJJ8HtdsPpdOLGG28MWLr2yCOP4IEHHmh0/cKFC5Vz1FoWLVrk9/oD+dJTY8O29avh2ufGsSNSgGHHdxu3Y37FVkzd9SiyAbxVfxoKC4uVSNPkHxN3p6En59jhvZg/f0+r97G1xxBJRPoxRPr+d4VjiPT9j7RjqKwMbi3viK4eHR1GQEeHEEKimSVLluDhhx/G008/rYILdu7ciVtvvRUPPfQQ7r333ka3F7dIeoCsjo4EGEjJW2qqEenc0rORsqiYPXs2YmMNt8bK/es+l1vhzJknYUR2CvKW7cOnh7YhpVcfzDsxGbFr1sEFG16pn41Lxg7HvFMHe+476Vg1nt5shBQIp06ZiHkTQh8s0NwxRAKRfgyRvv9d4Rgiff8j9Ri0q94cUSN0WLpGCCFdk4yMDMTExCAvL8/revk+O1t8j8aImJEyteuvv159P27cOFRUVODHP/4xfv3rX6vSNyvx8fFq80UWBW1ZGPi7v8y/OVZllGX06p6kfp5l9tkUVdQhdvVL6uv1iVOwrzobWeZtNP3SHYh32D3R0v3Sk9t18dLW1yAciPRjiPT97wrHEOn7H2nHEOx+RlUYAR0dQgjpesTFxeH444/H4sWLPde5XC71/bRp0wKWPfiKGRFLgpSydSZlNU64zF1ISzI+zDPMoaEVZcXAmv+or991nK0ue5rR0xq73eZJXhP6MIyAEBKlRJWjw3hpQgjpmkhZ2dVXX43JkyercAGZkSMOjaSwCVdddRX69u2rem2Ec889VyW1TZo0yVO6Ji6PXK8FT2cnriXFxXjm32ihM718IeAuA3oOw6dloyWKwfMzKyJ0ZGBoS4eFEkJIVyJ6hI7dWrrGMAJCCOlKXHrppTh69Cjuu+8+HDlyBBMnTsSCBQs8AQW5ubleDs5vfvMbNYhTLg8ePIhevXopkfP73/8e4TJDR0dLa9fGBhe+V/+xqsVwT7kBBR86vWbsWNGOTkuHhRJCSFciaoSOg44OIYR0aW655Ra1BQofsOJwONSwUNnCjWLT0dFla1r0nGLfgCH2w3DFdUPZiO+htn6514wdf0KnpcNCCSGkK+GIyoGhdHQIIYSEKSV+hE6M3Ybr4z8F3EDx8O+hpM4QNynxDiTENnZsZo/OwqnDe+Gi4/p24J4TQkh4ET1CR+Kldemas7qzd4cQQgjxS3GFUbqWZildQ+EuzHCvVl/uGngl3GU1AcvW9H1f/tGUjthdQggJW6JH6DB1jRBCSAQ5Oj0sjg5WvgA73PisfiJK7L2RUFHr6cEhhBAS5fHS0qNT7enRYekaIYSQ8KRRGEFNmSdS+uX6uSgor1Gb0DOZiWqEEIJoFzoxNrdF6NDRIYQQEp40hBGYn1nrXgdqSlEY3x9fusahoLwWBZ7SNTo6hBASiCjr0WEYASGEkPCmxOPoxMrkU2DFc+r7zTmXwX3Mrtwc3/k6hBBCotrRscZLM4yAEEJIeDs6qnRt9+dAwXYgLgUFQy5S14ujU2iWrlHoEEJIYOzRNUeHYQSEEEIiw9FR8dIrXzCunHgF0tJ7qi+lbE336DCMgBBCAhNVpWvVOl5aStfcbsBm6+zdIoQQQgI7OofWGFeOuwQZNuNknYgcPTuHjg4hhAQmeoSONXXNXQ/U1wEOngkjhBASPtQ461FZW98gdGrNntLEHsiINT6zCitqEe8wCjIodAghJDD2qJyjIzCQgBBCSJiWrdltQEqCo+GzKjbJEyVd73J7xFCggaGEEEKirEenDjGo14fsZCABIYSQcO3PiYPd7QRcxveITUScw45UET8m4uokxxklbIQQQqJY6EiPDmBDjVnjTEeHEEJI+M7QiQVqKxp+EJfcyMGRsjUbe00JISQg0SN0zM+CGiavEUIICVNKrEEE+nPKFgPExDXqyWHZGiGENE30CR2bTl6j0CGEEBJeFFuHhVr6c3RKaC+r0ElmoA4hhDRF1Agdh82tLqs9jg5L1wghhIRn6Vr3xLiG0rW4JM/Pe1rm5jBxjRBCmibKenSsQ0MZRkAIISQ8wwgMR6eqwdEx8S5do6NDCCFNEXWla15DQwkhhJAworjC7NGRsrS6iqaFDh0dQghpkqgTOlV6aCh7dAghhIRpj45KXdOfUwFK13pS6BBCSJNEjdAxh0ijio4OIYSQSEhdq7WEEfh1dFi6RgghTRF1jk6lR+jQ0SGEEBLGc3T8lK5ZU9esXxNCCGlM9AodJ4UOIYSQcA0jiPNbuiYBBHpGaC/O0SGEkCZxIMqEToUrzpB3dHQIIYSEEW63GyVVFqGj46VjEz23SYpz4N6zR6Ou3oU0uQ0hhJCAOKKtR6eaYQSEEELCkNJqJ+pdbkvpmu7RSfa63Y9OGtQZu0cIIRFH1JWuMYyAEEJIOAcRJMbGICE2xm/pGiGEkOCJPqHjGRhKR4cQQkj4RUurYaGCp3SNQocQQlpD1Akdlq4RQggJZ0fH03ujP6codAghpFVEjdCRlJrYGBuq3HR0CCGEhHHiWnKsd4k1S9cIIaRVRI3QERx2Gx0dQgghYT5Dx/ycYukaIYS0iagSOrExdlR5hA7DCAghhIRxjw5L1wghpE1EodBh6RohhJAw7tFJ1Cfk6OgQQkhbiCqh44ixoVrHSzspdAghhISfo6Nm6Ai17NEhhJC2EFVCh44OIYSQcOVYlRY6TF0jhJBQEFVCJ04cHfjUPhNCCCFhQE1dvbpMiDU/mlm6RgghbSL6HB1PvDTDCAghhIQPTpdbXTrsdu8TcixdI4SQVhF1PTqe1DWXE6g3ygQIIYSQzsZZ71KXMvMN9fIZZYQT0NEhhJDWEXWOTrXu0RFYvkYIISRMqKs3HZ0Ye0PZmkChQwghrSLqhE4tHHDrw6bQIYQQEiY4XaajY7c1fD7Z7IDDcoKOEEJI0ESZ0LHJpwbqY9inQwghJIwdnVpLEIFNPrsIIYS0lOgSOmaDpzMm0biCjg4hhJAwoc7s0ZF+Us+JOJatEUJIq4lCR0eEToJxBYUOIYSQMMFpOjpxqkeHiWuEENJWokzomI6O3RQ6TgodQggh4dWjoxwda+kaIYSQVhGVQqfOrnt0KHQIIYSEWY+OlFmzdI0QQjpe6Hz55Zc499xz0adPH9hsNrz//vtN3n7JkiXqdr7bkSNH0NGos2TK0WEYASGEkDCeo8PSNUII6XihU1FRgQkTJuCpp55q0f22bduGw4cPe7bMzEx0lqNTa2OPDiGEkAhJXSOEENIqHC29w1lnnaW2liLCJi0tDZ1JnOno1NLRIYQQEmbU+ZujQ6FDCCEdJ3Ray8SJE1FTU4OxY8fi/vvvx4wZMwLeVm4nm6a0tFRd1tXVqa2l6PvYYZwtq4EhdOprKuBqxeN1NHr/W3Ps4UKkH0Ok739XOIZI3/9IPYZI2tdIpt7lhtttqT6oo6NDCCFhL3R69+6NZ599FpMnT1bi5YUXXsBpp52Gb7/9Fscdd5zf+zzyyCN44IEHGl2/cOFCJCW1/o/+gf25Su4Ulteq77dtXIsdBfMRKSxatAiRTqQfQ6Tvf1c4hkjf/0g7hspKOt8dOUOnIXXNfN3Zo0MIIeErdEaMGKE2zfTp07Fr1y789a9/xb///W+/97n77rtxxx13eDk6OTk5mDNnDlJTU1t1RlIWFsOHDMLiQ/sQl9oTKABGDO6PYafNQ7ij93/27NmIjY1FJBLpxxDp+98VjiHS9z9Sj0E76qR9cbpMO8fj6LB0jRBCIqZ0zcqUKVOwdOnSgD+Pj49Xmy+yMGjL4iA+zrhvjRlGEFNfg5gIWWyE4vjDgUg/hkjf/65wDJG+/5F2DJGyn10lcU1wqB4dlq4RQkhEztFZu3atKmnraNSHh6VHh2EEhBBCwoFai9CJkc8qlq4RQkjHOzrl5eXYuXOn5/s9e/Yo4ZKeno7+/fursrODBw/ilVdeUT9//PHHMWjQIIwZMwbV1dWqR+ezzz5T/TYdTZzD0HVViDOucFZ3+D4QQgghvjjNaGmZoSOz5jgwlBBCOkHofPfdd5g5c6bne91Lc/XVV+Oll15SM3Jyc6Xp36C2thY///nPlfiRIIHx48fj008/9XqMjkINYQNQTUeHEEJIWAods9CCQocQQjpe6EhimltnYPpBxI6VX/7yl2oLB/QHSJXbdHQ4MJQQQkgYzdDRJdaezyeWrhFCSGT16HQW+gPEU7pGoUMIISQMaOTo1DKMgBBC2kpUCR39AVLpcXRYukYIISR85uioGTrqCpauEUJIW4kyoWN8gFS6tNBhGAEhhJAwEjp23aPD0jVCCGkrUSZ0jMOtoKNDCCEkDAeG6hNyLF0jhJC2E52OjtscgMceHUIIIWHk6DB1jRBCQkd0Ojr1DCMghJCuxlNPPYWBAwciISEBU6dOxYoVK5q8fUlJCW6++WY1wDo+Ph7Dhw/H/Pnz0ZlhBA75nKp3AvW1xg/ikjtlfwghJCrjpbuC0Cl3aUeHpWuEENIVeOONN9Rct2effVaJHBlWPXfuXGzbtg2ZmZmNbi8z3mbPnq1+9vbbb6Nv377Yt28f0tLSOmX/nWa8tKo8sH42xSZ2yv4QQkhXIKqEjk6zKdela64648xZTFS9DIQQ0uV47LHHcMMNN+Daa69V34vg+eijj/Diiy/irrvuanR7ub6oqAjLli1DbKzxmSBuUGdRpx0dGYPgqTawAY6ETtsnQgiJdKJqhR+nHR2nWbomOKuAmJTO2ylCCCFtQtyZVatW4e677/ZcZ7fbMWvWLCxfvtzvfT788ENMmzZNla598MEH6NWrF6644gr86le/QkxMTKPb19TUqE1TWlqqLuvq6tTWUvR99GV1jXEZY7ehruoYRHq5Y5PgdDoRrvgeQyQS6ccQ6fvfFY4h0vc/Uo8h2H11RGMYQUW9fIjJ127jzFk8hQ4hhEQqBQUFqK+vR1ZWltf18v3WrVv93mf37t347LPPcOWVV6q+nJ07d+KnP/2p+vD87W9/2+j2jzzyCB544IFG1y9cuBBJSa0PDFi0aJG6/O6ofCbF4FhxIZZ+tgozRVy5Y/BJJ/UMteYYIplIP4ZI3/+ucAyRvv+RdgyVlcG1n0SZ0DEcnVqJ8ZS6Z6mDZp8OIYREHS6XS/XnPPfcc8rBOf7443Hw4EH8+c9/9it0xC2SHiCro5OTk4M5c+YgNTW1xc8vgkoWFdInJKVzVasPAjs3oXdmJk4+MQPYCsR364F58+YhXPE9hkgk0o8h0ve/KxxDpO9/pB6DdtWbIyp7dFS6TZIWOkxeI4SQSCYjI0OJlby8PK/r5fvs7Gy/95GkNflAt5apjRo1CkeOHFGlcHFxlhJnQKWyyeaLPEZbFgb6/m6bcSIu1hEDh8v4XLLFJkXEoqOtr0E4EOnHEOn73xWOIdL3P9KOIdj9jMp4aTWvQM8moNAhhJCIRkSJODKLFy/2cmzke+nD8ceMGTNUuZrcTrN9+3YlgHxFTkfg9MzRsQG1ZqVBHGfoEEJIW4hKoSMTqN06spNChxBCIh4pK3v++efx8ssvY8uWLbjppptQUVHhSWG76qqrvMIK5OeSunbrrbcqgSMJbQ8//LAKJ+jU1DX5nOKwUEIICQlRVboWK7GdJm5HgoojoNAhhJDI59JLL8XRo0dx3333qfKziRMnYsGCBZ6AgtzcXJXEppH+mk8++QS33347xo8fr+boiOiR1LXOQFUa6M8pCh1CCAkJjmh0dAS3Qzs6DCMghJCuwC233KI2fyxZsqTRdVLW9s033yAckEoDTy+pPgHH0jVCCGkTUVa61uDouDxCh44OIYSQzsXj6MgJudoK48rY5M7dKUIIiXCiSujIIDabqXVcMQkNA0MJIYSQTkSlgWqh4yldM0/IEUIIaRVRJXRsNhtizRrtejo6hBBCwoQ6M/3NoXp0WLpGCCGhIKqEjrV8zRVjzkNgjw4hhJAwcXQcXqVrFDqEENIWok/oOMyIaV26RkeHEEJI2PToMHWNEEJCRfQJHT1Lx87SNUIIIeGBZ46OlFdrocPSNUIIaRPRJ3TMWTp0dAghhIQLTu3oOGxALR0dQggJBdFbumbXPToUOoQQQsJjjo4KzNGfSxQ6hBDSJqK2dK3Oph0dhhEQQggJjx4dY2CoGUYQxzk6hBDSFqJO6KjoTvlQ8aSu0dEhhBASTqlrnKNDCCGhIOqETpxZulZrY7w0IYSQMEtds87RYekaIYS0iagtXavRpWvO6s7dIUIIIVFPncvi6OjSNQodQghpE1EodGw+jg5L1wghhIRJ6pp8RunSNcZLE0JIm4hCoaNL1+KMK1i6RgghJEx6dGLtbqC+xrgylmEEhBDSFqJW6NSAc3QIIYSEB3Uuw9GJd1nKqRlGQAghbSJqS9eqQUeHEEJIeDk68ag1r7FR6BBCSBuJQqFjHHK1p0eHYQSEEELCI3Ut3mVJXLMZJ+YIIYS0jugVOjCFjtRCu+o7d6cIIYRENVroxOnSNbo5hBDSZqJQ6BhnyKrcsQ1Xsk+HEEJIJ+I046XjYAodJq4RQkibiUKhYxwyhQ4hhJBw69GJq9eODoUOIYS0lagVOk6XDXCYpQEMJCCEEBIGpWsOa48OIYSQNhG1pWvqQ0XXQDsZSEAIIaTzS9diXeYMnTjO0CGEkLYSvQNDldAxz5jR0SGEEBIGjk6sUzs6DCMghJC2ErVCx3B0ODSUEEJI58PSNUIICT1RW7qmGj/1GTM6OoQQQsIgjMChwwhYukYIIW0mCoWOv9I1OjqEEEI6B7fb7enRiXGaJ95YukYIIW0maoVOnZejwzACQgghnYMWOUIM46UJISRkRJ/QcZhCx8kwAkIIIeFTtibYPY4OhQ4hhLSV6BM6drNHx+UCHAwjIIQQ0rnUyeeRiV2nrsVR6BBCSFuJ4h4dKV2jo0MIIaRzURUGvkKHjg4hhLSZKC9d0z06dHQIIYR0Dp4gArsNNn3ijUKHEELaTNQJnTgdL+2yCB19Bo0QQgjprBk6Ulpdawodlq4RQkibiTqh47D7K12j0CGEENK5YQSqtNrj6HCODiGEtJUoL11jGAEhhJDORVUYyIk4qTjwCB3O0SGEkLYSfULHLF1TpQIMIyCEENLJqLluuuJAn3hj6RohhLSZKBQ69obmT4YREEIICZMeHXUirrbCuJJhBIQQ0maiVujUeg0MpdAhhBDSuY6Od48OhQ4hhLSVKC9do6NDCCGkc3FqR8fuBpzVxpVxDCMghJC2EnVCJ850dJTQcTCMgBBCSHjM0elmr2u4kmEEhBDSZqJO6Dh0j45XvDTDCAghhHRuj043e03DlQ4KHUIIaStRW7pWy9I1QgghYTRHJ0k7OnISzpz5RgghpPVEd+madnScFDqEEEI62dGxmY4Oy9YIISQkRG3qmpRE17NHhxBCSCdTZ/boJNtqjStiGURACCGhIOqEjpo8bVJnN4WOpNyYk6kJIYSQzkhdS6KjQwghISXqhI52dIQ6W1zDD1i+RgghpBN7dDyOThxn6BBCSCiIbqGjHR31DYUOIYSQjqfOrChIgHZ0WLpGCCGhIOqETozdBrtZveaUzxb26RBCCAmH1DWP0GHpGiGEhIKoEzpWV4cR04QQQsIldS3RVm1cwdI1QggJCVEpdBoipjk0lBBCSOeiPoukdM3N1DVCCOlUofPll1/i3HPPRZ8+fWCz2fD+++83e58lS5bguOOOQ3x8PIYOHYqXXnoJnUmswzJLh6VrhBBCwiB1LRGmo8PSNUII6RyhU1FRgQkTJuCpp54K6vZ79uzB2WefjZkzZ2Lt2rW47bbbcP311+OTTz5BZ+Ewm3S8hobS0SGEENKJc3Ti3WaPDkvXCCEkJDhaeoezzjpLbcHy7LPPYtCgQXj00UfV96NGjcLSpUvx17/+FXPnzkVn9ugYpWuJDbN0CCGEkE5ydOI9jg5L1wghpFOETktZvnw5Zs2a5XWdCBxxdgJRU1OjNk1paam6rKurU1tL0ffRl7Hm0NCqmlq4HAnK1nJWl8HdisfuCHz3PxKJ9GOI9P3vCscQ6fsfqccQSfsaqTi1o+Ni6hohhESU0Dly5AiysrK8rpPvRbxUVVUhMbHxH/RHHnkEDzzwQKPrFy5ciKSk1lv6ixYtUpfVlTEAbPh62TcYWFSG3gA2rF6B3H3h/eGi9z+SifRjiPT97wrHEOn7H2nHUFnJst72plbNOgDi3Dp1jY4OIYREhNBpDXfffTfuuOMOz/ciinJycjBnzhykpqa26oykLCxmz56N2NhYPLtnOY5UleG4yScga0N/oHQNxo8cirEnzEM44rv/kUikH0Ok739XOIZI3/9IPQbtqJP2w2kODI3XQoeODiGERIbQyc7ORl5entd18r0IFn9ujiDpbLL5IguDtiwO9P3jYsXRAVywwx7fTX0d46pBTJgvPNp6/OFApB9DpO9/VziGSN//SDuGSNnPrjAwNM6lhQ7DCAghJCLm6EybNg2LFy/2uk7OaMr1nUVcjDV1TQ8MZRgBIYSQzpujE6uFDkvXCCGkc4ROeXm5iomWTcdHy9e5ubmesrOrrrrKc/sbb7wRu3fvxi9/+Uts3boVTz/9NN58803cfvvt6CwcdjN1TRpAPUKHdeiEEEI6r3Qttt6c58bSNUII6Ryh891332HSpElqE6SXRr6+77771PeHDx/2iB5BoqU/+ugj5eLI/B2JmX7hhRc6LVraa2Co0zpHhwNDCSGEdF7pWqwndY2ODiGEdIrQOe200+B2uxttL730kvq5XC5ZsqTRfdasWaMio3ft2oVrrrkGnYlX6ZojwbiSQocQQiIaGWQ9cOBAJCQkYOrUqVixYkVQ93v99ddhs9lwwQUXoDOoNefoOLSjw4GhhBASGT064YhnYKgqXdOODkvXCCEkUnnjjTdUhcFvf/tbrF69WlUQSOVAfn5+k/fbu3cv7rzzTpx88sno7IGhHqHD0jVCCAkJUSl0HDHW0jXzA8XJMAJCCIlUHnvsMdxwww249tprMXr0aDz77LNq7tqLL74Y8D719fW48sor1dy2wYMHozMHhtrggsOTusbSNUII6bJzdNqbWK/UNTo6hBASydTW1mLVqlUqDEdjt9sxa9YsLF++POD9HnzwQWRmZuK6667DV1991eRzSOm1bL7zhWQ2kmwtRd9HLmud9UhAbcPPbA75AcId6zFEKpF+DJG+/13hGCJ9/yP1GILd16gUOnHa0fGKl2aPDiGERCIFBQXKncnKyvK6Xr6XtE9/LF26FP/85z89CaLN8cgjjyjnx5eFCxcq56i1SFBPfkEMktAgouYvWgLYIqfgQo4h0on0Y4j0/e8KxxDp+x9px1BZGZxB4YjqHh1JuonVYQR0dAghJBooKyvDD3/4Qzz//PPIyMgI6j7iFkkPkNXRycnJwZw5c9QA7NacjZRFxezZs/HP/atQV16grnc7EjHv7HMQCViPIVIHy0b6MUT6/neFY4j0/Y/UY9CuenNEpdBx+C1do6NDCCGRiIiVmJgY5OXleV0v32dnZze6vaR/SgjBueee67nOZc6ycTgc2LZtG4YMGeJ1n/j4eLX5IouCtiwM5L7OeiDRdHRssYkRs9AI1WsQDkT6MUT6/neFY4j0/Y+0Ywh2PyPHG2/30jWGERBCSCQSFxeH448/HosXL/YSLvL9tGnTGt1+5MiR2LBhg2f4tWznnXceZs6cqb4Wp6ajB4ZqoYM4BhEQQkioiEpHx7t0jWEEhBAS6UhZ2dVXX43JkydjypQpePzxx1FRUaFS2ISrrroKffv2Vb02Mmdn7NixXvdPS0tTl77Xd9TA0CSbHhbKGTqEEBIqolLoeJeuMYyAEEIinUsvvRRHjx7FfffdhyNHjmDixIlYsGCBJ6AgNzdXJbGFI3VWR4czdAghJGREuaPjAhx6jk6V1DpIJmnn7hwhhJBWccstt6jNH0uWLGnyvi+99BI6C3F0WLpGCCGhJypX9QmxMeqysrbe++wZh4YSQgjpYOq8Stfo6BBCSKiISqHTPdFIajhWVUehQwghpFOR6oKG0jX26BBCSKiISqGTZgqdUhE69hggxowMZSABIYSQDsZpFTosXSOEkJARlUKne5IhdEpE6AgMJCCEENJJ1LlYukYIIe1BVDs6qnTNS+jQ0SGEENKJjg5L1wghJGREdY+OlK65XDJLh44OIYSQjkc+g+RjKBG1xhUsXSOEkJARlUIn1RQ68uFSVuO0DA2l0CGEENKxZWsCS9cIIST0RG28dEKsvSGQgI4OIYSQTipbExI8pWt0dAghJFREpdAR0hLj1GVJpVXosEeHEEJIx87QEZI8QoeODiGEhIqoFTpes3QcdHQIIYR0PE6Xy7t0LY5hBIQQEiqiXuiUVNWydI0QQkinOjqeMAKWrhFCSMiIXqGTZHF0dBiBk0KHEEJIxzs6yQwjIISQkBO9Qkc7Ol49OhQ6hBBCOg6ndnQ8pWt0dAghJFRErdDRQ0O9U9cYRkAIIaQThA4HhhJCSMiJWqFDR4cQQkhnU2vGSzcIHZauEUJIqIhaoZPm1aNDR4cQQkjH41QDQ90NQoela4QQEjKiVuikWuOldalAXXXn7hQhhJCoGxiaoBPXBDo6hBASMqJW6KQlmQNDvRwdlq4RQgjpWEfHMyxUYI8OIYSEDHu09+iUejk6LF0jhBDSsXN0PGVrjgTAHtPZu0QIIV2GqBc6JZW1xoeLQEeHEEJIB8/R8URLs2yNEEJCij3a46UrauvhjNFCh44OIYSQjqPOaSldi2UQASGEhBJ7tIcRCOUuo18HToYREEII6WBHR4cRxLE/hxBCQknUCp0Yuw0pCQ71dbnLFD0sXSOEENLRPTo28yQbS9cIISSkRK3QsfbpHHNqocPSNUIIIZ3k6LB0jRBCQkpUCx09NLS0zky5oaNDCCGkA3HWW3p0WLpGCCEhJaqFjnZ0ij1CpxJwy5RqQgghpP2pc0npGlPXCCGkPYhqoZOWaIQQFNUavToKp2VwGyGEENKO1NVL6RpT1wghpD2IaqGjk9eKai0D2tinQwghpDNK1+joEEJISIlqoaN7dIqrXUCMGTHNPh1CCCEdhFMcHV26FkdHhxBCQklUCx1P6lpVHeAwz6RR6BBCCOnIHh2Po8MwAkIICSUUOiJ0KusaSgZYukYIIaSDYOkaIYS0H1EtdNKsjo7+gHGag9sIIYSQjpijYzPn6LB0jRBCQkpUCx3t6JQooWOWDNDRIYQQ0kHU1bN0jRBC2ovoFjpJfhwd9ugQQgjpwDAClq4RQkj7EN1Cx9Kj445NMK6ko0MIIaQzBoaydI0QQkJKVAudtCQjUrq23gVXDB0dQgghHR9G0FC6RkeHEEJCSVQLneS4GMTYberrWnu8cSWFDiGEkE4pXaOjQwghoSSqhY7NZvMkr9XazNK12orO3SlCCCFRVbqW4CldYxgBIYSEkqgWOtY+nbLEfsYVG94GXC715e6j5Xj1233qjBshhBASauoYRkAIIe1G1AudVFPobB9wGRDfHcjbAGx4U1133web8Ov3NuKzrfmdvJeEEEK6Ik6nC4kw5+iwdI0QQkJK1AudNDNiusDVDTj5duPKz34H1FVjy+FS9e2O/PLO3EVCCCFdFFt9New2t/ENS9cIISSkRL3QsUZMY+qNQGpf4Nh+VH39LAorjLNs+wrZt0MIIST0OJyWABwODCWEkJAS9UJHhxF4hobOvEd9H7vsMaTCcHL2FXK2DiGEkNBjd1Wry3p7HGCP6ezdIYSQLkXUCx3t6JRUmTXSEy4HMkfDUXsMP3X8n7oqt4hChxBCSOhxOI3PF88sN0IIISGDQsccGnqsymlcIWfUZt2vvrw2ZgF6oxBHSqtRXVffmbtJCCGkCxJTbzo6DgodQggJNRQ62tGpNB0dYdgcbE+YgHhbHe5wvAW3GzhQTFeHEEJIaHHUGz06LvbnEEJIyIl6oaN7dEqlR0djs+GJmB+qLy+O+QojbLnYW0ChE+m43W4s2HiEopUQEjbEmj06Ljo6hBAScqJe6HRPsoQRmNS73PjkWD/8r36qiv38leN17GOfTsSzcm8xbvzPKtz1zobO3hVCCFE4zNI1N4UOIYSEHAodTxhBg9A5WFyFWqcLj7svR70tBqfHrIV971eduJckFOw1Y8IZLkEICRfi3EbpmpvDQgkhJOREvdCxlq65XMbQtl0FRqx0TM8h2NX/e+rrU3OflNqnTtxT0lYKy40+rCJzPhIhhHQ2cdrRkfEGhBBCQkrUC51UU+iIximrMZLXdh81zvwP7pWMYyfcjnJ3AgbXbgM2vdep+0raRmF5jbosr3GixskUPUJI5xPrNv4ucVgoIYSEnqgXOgmxMUiItXsFEuw+Wu4ROr379sdzznPU9+7FDwLOxm7AGytz8eZ3+zt0v0nLKbQ4OcUVlvAJQgjpJOLdhqNjo6NDCCEhJ+qFjnfEdJ23o5PRDb27J+JlnI2j7u6wFe8BVr3kdd+80mr86p0N+NU761kSFeYUmI6OwPeKEBIOxLuMHh1bHHt0CCEk1FDoqD4dPTTUFDoFDY5OjN2Gnj3S8bjzYuPGX/wBqC713HdNbom6lPadrYcbrifh26MjUOgQQsKBOF26Fk+hQwghoYZCxyt5rVb1b+SVGh88g3t1U5cDeibhjfrTUJo8EKgsBJb93XPftfsNoSNsptAJaworavx+TQghnYGcIEswhY6NPTqEEBIeQuepp57CwIEDkZCQgKlTp2LFihUBb/vSSy/BZrN5bXK/cJ2ls8csW8voFucRQAN6JsMJBxb1+Ylxh+VPAUe3qS/X7i/2PM7WI2Udv/Mk6GGhVkenmI4OIaSTcQFIshlCxx5PoUMIIZ0udN544w3ccccd+O1vf4vVq1djwoQJmDt3LvLz8wPeJzU1FYcPH/Zs+/btQ7j26OzSQQQZhpsj9E83PoA+dU0BBp4M1FUC/74I9SUHsf7AMc/tttDRCZrqunq8+u0+HCox6tPbm9IqJ5xmfLjA0jVCSGdT7wISoIVOw2cOIYSQThI6jz32GG644QZce+21GD16NJ599lkkJSXhxRdfDHgfcXGys7M9W1ZWFsJR6EjqmjVxTSOla8K+oirgey8DPYcBpQdQ98qFcNSWqj4eYUdeOerkk4s0y4frDuHX723E7z/a0iHPV+BTqlZUSaFDCOlc6t1Akil0YuLo6BBCSKcKndraWqxatQqzZs1qeAC7XX2/fPnygPcrLy/HgAEDkJOTg/PPPx+bNm1COA4NldK1XQUNM3R8hU5uUSXcSenAD94BumUhoWgbnot7DNMHJKNbvAO19S7sMe9Pmia3sLJRj1N7Yi1bE+joEELCQegk2oy/RTEMIyCEkJDjaMmNCwoKUF9f38iRke+3bt3q9z4jRoxQbs/48eNx7Ngx/OUvf8H06dOV2OnXr5/f+9TU1KhNU1pqlITV1dWpraXo+wS6b7d4Q+8VVdQgV1wbKVfrkeC5fXa3WNhsxqDJvGOV6NmtD3DZG3C+eDZOxBZ0r/4r7s28A9/tL8OG/cUYlB7aHqTm9j8S8D2GI8eM1/lgSRXyj1WgR5KRfNdeyPtmpaCspkWvZ1d8DyKNSN//SD2GSNpX6R/985//jCNHjqiy6ieeeAJTpkzxe9vnn38er7zyCjZu3Ki+P/744/Hwww8HvH27CR1dukZHhxBCOlfotIZp06apTSMiZ9SoUfjHP/6Bhx56yO99HnnkETzwwAONrl+4cKEqk2stixYt8nv9ngIpPYvB7gN52Kcq12zYv+k7zN/dcJvusTEoqbXhjf99ioEp5v64bsVj7j9hVPHnuCHOge9wDeYvWwfHwTWt3sfW7H8koY9h024Rl4bAfOmDxRjRvaF/pj1YesR4j+Nj3KiptyE3rwjz58+P6vcgUon0/Y+0Y6is9D5JEK7o/lEpp5aQnMcff1z1j27btg2ZmZmNbr9kyRJcfvnl6jNJAnL++Mc/Ys6cOeokXN++fTuwdM0YGArO0SGEkM4VOhkZGYiJiUFeXp7X9fK99N4EQ2xsLCZNmoSdO3cGvM3dd9+tPrCsjo6UvcmHkAQbtOaMpCwsZs+erZ7fl247CvDKjtUoqItDnasODrsNV15wJmJjGir7Xj28Eiv2FqPvyEmYN6G3cndu+8YB2H6KJ+OewNzaRbgppic2JV+LefOOb/E+tmX/IwHfY3hmj5Q6Gil1KTmjMO+kge36/Ls+2wXs2YVRfdKwdv8x1NnjMW/eaVH9HkQakb7/kXoM2lEPd6z9o4IIno8++khVFNx1112Nbv/qq696ff/CCy/gnXfeweLFi3HVVVd1yD67LKVrYLw0IYR0rtCJi4tT9r58EFxwwQXqOpfLpb6/5ZZbgnoMKX3bsGED5s2bF/A28fHxavNFFgZtWRwEun/PlER1WWIODO3fMwlJCd7PPzAjWQmdgyU16jG25B5TMxDWdp8J22l9gQW/wq9iX8cDh3oiNvbEVu9ja/Y/ktDHUFDeUJq4Na+83Y+ruMqpLodnpSihI+91TIwDdjNIIhrfg0gl0vc/0o4hEvZT94/KSbKW9I/6OlciRNPT0zuspNpaulZni5UrEUlEYilmVzuGSN//rnAMkb7/kXoMwe5ri0vXxGm5+uqrMXnyZFXLLOUBFRUVnrNociZMbH8pPxMefPBBnHjiiRg6dChKSkpU/bTES19//fUIt9Q1zRBzUKgVmaUj7Cuq8Gqin5iTBpx4I2pLDiLum7/j185nULr+JKSODyzkoh1nvQuFljCATYfa/4yxHhA6LNOoO6x3uVFaXYe0du4NIoS0P63pH/XlV7/6Ffr06eMVttPeJdXOercndW3xV9+gJja4fQ03IqkUs6seQ6Tvf1c4hkjf/65aVt1ioXPppZfi6NGjuO+++1TD58SJE7FgwQLPB0xubq46k6YpLi5W5QRy2x49eihHaNmyZSqaOtxS1zTWxLVGEdNmWtiaXIvQEbdr7oP45Lv1mOtcguQPfgSkfwT0C20JW1ehoLxWuWEaifSuqq1HYlxMuz6n0DstQSXkSemhiC0KHULIH/7wB7z++uuqbyfQQOv2KKl++Z2PYLcZfwzPOPNcIN5sAI0QIrEUs6sdQ6Tvf1c4hkjf/65eVt2qMAIpUwtUqiYfFFb++te/qi2cSfV1dCzDQjUD0k1Hp7ASbre7wdHpbwgdiWX7oP89SNhRhFOxHvjv94DrFgE9h3TEIUQUeaVG821WarxyVkSEbD1Sikn9e7TbcxaapXI9k+ORnhynhE6xuEq92u0pCSEdRFv6RyUJVITOp59+qtJBO7Kk2u5qKIWLTUwFYto9HwjRXorZVY8h0ve/KxxDpO9/pB1DsPvZ4oGhXREZ+JkS72jS0ZG+HUF6S3YdLcfRshp1v7F9untuM6JPOm6quw25CSOAykLgn7OBDW/Dy74gyC8zPtyzUhMw2nz92rt8TZfKZXSLU0LHeh0hJLKx9o9qdP+oNfXTlz/96U8q/VOqEqQcu6OxuYy/QbVyzjFCRQ4hhIQzFDom3ZMalOFgPz060seTZt7mw7WH1OWo3ile5VbyfSUS8Kv4e4GscYbYeec64PUrgNLDjR5TnKFDJVXqsqPYV1iBc59YindXH0BnkV9mODqZKfEY0ye13YVOXb0LJZVG01rPboajIyhHhxDSJZCyMpmN8/LLL2PLli246aabGvWPWsMKJE763nvvValsAwcOVOXVssmA644ipt74G1SDxk4RIYSQtkOh4xNIIGJGL4R9GZBuuDofrDvk1Z+jGdXbWLSvKnCg7rpPgdPuBuyxwLb5wFNTgdWveLk7H204jOl/+Az/+NIysKedeXHpHmw4eAwvL9+HziK/1HB0MlMTPEJn86Fj7fZ8WtBIwJr0Y9HRIaTrIf2jUoYm/aPSO7p27dpG/aOHDzeccHrmmWdUWtsll1yC3r17ezZ5jI5Cl67V2EI7ZJoQQogBvXIT7dYMzgg8tK1/z2SsO3DME0gwMce7p6Rfj0RPo/uuolqMPO0uYNR5wAc3A4dWAx/+DNj4DnDu34EeA7B8V6G635sr9+PGU4d0SNqZiCthZ16ZcpJstpbFK4fe0TFK17YeKVP757DMLgp1EEF6cryKk6ajQ0jXpCX9o3v37kVn43F07BQ6hBDSHtDR8XF0/JWt+To6Gl9HR0TDyGwjNWfrYWMYJrJGG6EEsx8CHAnA7iXA09OAb/+BA2ZU9e6CCtX30958s7vIs+ivqK3HoWPmRO7OcnRSEtRrKuKwxunCrqPG69Fe0dLSnyNooVNEoUMI6URiTEen1sbSNUIIaQ8odEz6m6lq4/s1hAsEipgWUhIcft0fXb625bCl50SaTGf8P+CmZUD/6UBdBfDxL/Hzg7djsM0og1u8xTstqD34cN1Br+935JlirJPCCMTREYdFepuETe1UvlZoiruePkKHpWuEkHAQOnR0CCGkfaDQMfnZ6UPxr2tOwGUn9A94Gz00VLs5skgPJHQ2W4WORqKmr/kImPcXuOO6YbxrMz6OuxuPxj6N0tXvArXt42gINc56fLzxiPq6d3fjQ3VnfmAXyeVyo6y6rl1L1yR1TdDla+0VSCBJeTpaWkg3Z+cUV1LoEEI6D4d2dOyJnb0rhBDSJaHQMUmOd2DmyEzEOexBOTq+ZWuakaY7IT0nfpFhqlNuQMFVS/BF/XjE2+pwccxS3FnyO7j/NBj472XAmv8AFQUIJV9sO4qyaqeaXXPJ8f3UddubcHSe+Gwnxj+wECv3FoV0P2RujkRzC5mphvAY7UleaydHp8LH0TEvtdNDCCGdQYzb+FtYxzACQghpFxhG0AKk1Coh1o7qOhcm6UGhPkiPjvT3y2JenISMbv5rr/c5e+Lqul9hXuJenGFbiROqvkZ/51Fg+8fGZrMD/acBI88Ghs5t875/aCbFnTO+D0aYfUQ7mnB05m84rALiRCCdMDAdoUL6YlxuNV8VPc0SsobktdJ2CUjQw0L1e0FHh3QFlmzLV/9P7z9vDJLi+Kc8Eok15+jUxVDoEEJIe0BHpwXIAvxHMwZhxtCemDY4w+9tZMEx0Cxx8+rT8WF/sSS32VCcMRl7jrsHp9Q+jt/1fwE47R4gezzgdgH7vgY+uQexTx2PU7feC/vql1pV3lZR48SnZg/QeRP6YHiWIXR25pX7neFTWevEjnzD7dlTWNEu/TkiOnTC2rDMFMTG2FBa7cSB4iq0W49OsrejU1lbj+q6+pA/HyEdwd8W78Cb3x3Ap1vyO3tXSBt7dOpYukYIIe0ChU4L+eWZI/Hq9Sd6DQr1pVHymh/2FxkL+pz0RMwaLXMebHh9XypqTroTuPEr4LYNwJl/AAaeDLfNjrSqfYj5+E7gsVHAJ78GivYEvc8icsSFktI7CVsQIRZjt6GsxokjpY2T16RXRlwXYU+Ik9CsQQQaKRfU4qs9ytcKPKVrxnOmxDuUsBIYSEAilTwzNfFgO5wcIB1DrCl06unoEEJIu0Ch0w74TV7zYX+RMYsnp0cSxvftjl4p8Wr+zre7zZ6YtP7AiTcB1/wPztu2YEPfK+DuMQioPgYsfxL4+yTgv5cCOxdLckCT+/N/ZtnaueP7KFdKhMVAs99oR17j8rV1+0s8X+8trPDr+rSWo36EjrV8rT0CCXTpmu7Rkdeghy5fo9AhEYj8nzxq/l4fPkahE6nE6h6dGDo6hBDSHlDotANNJq95la6Jo5Ok0tvOGJkZOGY6qSd2Z54J503fAle8CQw5Q5Y6wPYFwH8uAp6aoubyoLrx85VU1uKL7UfV1+dN7OO5Xjso/vp01h9ocFWkvEuLk9A6Ot5nMNszeU2XrmWYqWsCI6ZJJFNSWYe6euMExKESCp1IFzp0dAghpH2g0GkHdOmaDAGtq3c1W7omzBqVZZaZ5Qd2UCSgYPhc4IfvArd8B0z5CRCXAhTuUHN58Nho4N2fGKIn91vVz7Ng4xG1IJJ90uJGGJbZLeAsnfUHGhwdPdA0VOiz0JL+5t/Rab50TZyyj9YfDur5pN+oyuzD0Y6OVejQ0SHthfw/dgb4/x+qEwbCwZLOGfxLQhdGUO+go0MIIe0Bo3ragX49EtEt3qFK0fYUVHgJDEHEjy436dfDKCGbMTRDJbodLKnClsNlnsjlgGQMA+b9CTjjXmDta8CK5wzBs/51YxNsdpwU0w+PxvZHZs+pwL5YIHscEN8NQwM4Oscq67C30HCbJvTrjnUHjmFvQQVOHNwzJK9NfqmxQOtlztCxumAStpZX2nRanXD7G2tVfHdm6rRmE+G0myOvbZKlr4qODmlPZA7VhU9/jYraerxz03R0T4wN6eNbXVY6Ol3B0WkYXUAIISR0UOi0A9IDMjyrG1bnlqgFua/QOVxSrZr9pVeml7mgl3CDk4ZmKEdHyteaFTqa+BRg6o/VbB7s+dJIaju0Fji8FijPQz9nLvrF5AK7lgK7HlWhB8gYjtOTc/A7hxvFeb3gXrMHtu59gdR+2HTUEAD905PUrCAROqFMXssv99+jI3OMBvVMVu6RlK+dOrxXwLPk2mH6akdBs0LHOizUGlutE9iKKkJXlkeIpqCiRv3fEf60YCt+f+G4dhm6KxyrqlMnVeTkCoks4kyh43KwdI0QQtoDfjK2EyOyUw2hc7hURTr7688R50f6czRSviZCR1LSfnbGsJY9oSziB59qbCafrViH/7z/IU5PPYQf9C82xE/ZYaBgG7oVbMMP9Lv/gekAAZgOYG18MiqcWXDn9sZgRxISdg4Dtp0CSBhCj4FAbOs/lI+WGQ5Klo+jI4i4M4TOsYBCR+bw1DqNcqBvdxcG359jKVsTeniETl0rjoKQpsk71iCgX/02FxdO6ovJIZxH5ds3d7ikCsN8TqiQ8CfObfx9cjno6BBCSHtAodPOfTrbjpQ1mbhm5XQzkEDOBOeVVvsVAy1hVXE8PnMdh8xh5wMXjzeuLMsDjmwAju3HK58sQ0JVHmb3q0cPGVZaehCoLUearQJptbuBwt24Wn5DChcBrz1tPqoNSO1jiJ50c+ueAySlq9AEJJqXccmG+LIgLlZBAEdHGGr2DenXxx+HzUhdYc3+EjUHJyE2cNR3oenY6GhpDR0d0p74xrbf/e4GfPT/Tpb/PSHv0RGk5JVCJ4IdnVgKHUIIaQ8odNqJEXqWjj+h40lc825AzUxNwAQpF9tfgrdXHcDNM4e2aR90gtmYvkaimSIly9gAfL1lHD7ZlIffjhuNa2cMUtfNevj/gLJDeOLsLGTW5+ONRV9hkD0PZ/apgk1m99SWGYJItn1LAz95THyD+ElKR0xCDwwqisMQ1/HYihy/PTh90hKbba62Ch1xdtbuL2myf6jAZ1ior6NTTEeHtKPQmTIwXYWSSC/cc1/uwk9OHtg+jo7l/wWJHOJNoeNmGAEhhLQLFDrt7OjImday6jqkJMQ2TlzzcXSEH0ztr4SOTD2fOyYLQzNbd5ZWelk2HjzmlWjmy7DMFCV0dCCBLJ52ltphs/VDzglzkeCw468L+6Gu1o2ll85EPxEilYXGsNKi3UDxHmzetBaxlXkYmlwDW1UxUFkA1NcC9TVGmZxsZrzfRAAL4t9CHtIRN3+hkSA36FQVjiD0NYVOU83VR45VwQ4X4lCHasSruUNNCR1duubr6DSEEdDRIe03zFNOeFwxtT9ue2Mt/v7ZTswd7b8ks7U9OqkJDpRWOxlIEKHEwRQ6scmdvSuEENIlodBpJ9KS4pCdmqDO7G7PK8PxA9L9ztDx5ZLj++F/6w+r2Tc/f3OdSmxqbWmLuBnSAjQqO4DQyfKOmN5w0IiVHtKrm6exWfZx99EK7C2oNBLikjOMLecEFc08b8EidbtXL5yqkuMg0di1FUBVkSGK1FaM+mMHsWfZe+hbuRlZtiJg9cvGFhMHDJgBDJuD/hknqcc6VFIJd1kebCW5QMk+oHivebkP5xzaicvijyDWVo8ydyLKvukJHBgEdBOnKtvYupmXKdkoKyvx26OjhY70/DSJDGg9uh22I5sw/MhXsK88CCSnAwndG29x3RqV65HoRDss2d0TcP7EPnhn9QEVnnHfh1twqVGhGhJHRxxgeVw5oUIi19FpS98jIYSQwFDotCNyNleEjpSveQmdJhwdSQb748XjMeevX6henWe/aF25i55HI6JFEt0COTrC9rxy5QCt22/cZ3y/hlI3lYR2tAJ7Cspx0rAMr/uv2lfs+XrR5jxD6MhCXxwa2dL6e37uqqvDi1sH453ielyfcwi/GLQP2PGJIWJ2f662HAkYiE9DCqpge9S/09JDvUjG1ym2KqQ4DwB7DwR8Hf4M4K74FNhX9QcODzX6idIGIDs+CyNsh3CoKgP1LjdiqouBo9uAo1u9L8sOef6jjJIvDr8d+EW3xQAJqUCvUcBxVwFjLgBiWZLS5Tm6HVjzb0Pcj78MGHiS6rET5GSH/J/+3QVjMeevX2L57iIMtttwdoh6dCaZQoeOTgTidiNeOzoMIyCEkHaBQqedy9fEmbEGElTV1nsa8n17dDRyFviB88fg9jfWqRK2U4a2fIbNxoNGf85Ya3+OD4N7JSvHR+Jpxf3Rg0In9Evz3GZQhlFSsaegcUDAyn1FXkLnt+eO9opw9uVYLVCDOORlngTMuxlw/xEo3Als/wTYsRDYtwxZLmMf3LDBJqEHaQOAHgM8l/d/VY5PDibgNxdPwT8//gZx1Ufx+1m9MCShDCg7YmzleebXh1W4Qk9bGVCyydhM5Ag/MavZ3A//P8AZOAABKX3gyhiO/cdcyMnqAbv0KYnTo7eqElFygLsekPK93GXGtuAuYOKVwPHXAL2GB358EnnUVgKb3wdWvwLkLm+4fs1/gMwxmFQ6EysxWf1fFgb0TMats4bhTwu24eP9dtzfhqeWAI6yaqfH0REOcWho5FFfixgYw6Ft8SxdI4SQ9oBCp4MDCQ6WGAvqlHhHk0MEL5jYFws2HlE9NL98ZwNuGNg6RydQf44gaWUyL0cGhEr52gazp2ecxdEZaAqdvX5m6aza2+DoBDPotLTOEEFZqabCEFEkg09lm34LUFOGXz39Gr7Nt+PXl8/B7PENjpDm80Wf4zAq0SsjE9lDxmH+hiP4GMNxy3T/cdwzf/cBEioO4h/n9kJ/eyEg5XDHpCQuFyWHdyEN5bBpkdO9P9BrhLmNNLfhqiytvq4Oa+fPR59582CP9XnfpFzPWW2InsoiYPvHwHcvGc/zzVPGNvBkYPK1wMhzAYd3GV0kDL+Uwaq9/CTlRR2H1wGrXgY2vAXUlDY4edJv1i0TWP8mkL8JP8cm/EhczU0icm8CuvfDlVMHKKFTVGNTQiXd9/eohWVr8Q67Z0aXDCCW98kaV0/CnLqGkys2pq4RQki7QKHTAUJHHB0pDRO3Q5et9UtPatL9kJ/JkMGVe4uxNa8cnzjsOK8Vjs6YPoEdHUEiaUXoiPMkro7DbsPo3g1iZbDH0alodFZ5vTkQUYajSvmbuDpNCh2zHSYzJUA9enwKjmUcj715R3CwrL7Rj+U11L0PvbsnYOqgnkrofLO7CLec3vjhZOGXWxmLevcAxI89Qzq3vX5+4V+WIL+gAP+9bAAmjBrlCUWQ5xHhJn08SXFB/BeR91FK1GST3qCs0cCM24BdnwHfvQhsXwDs/crYknsBk34AHHe1Ec1tPCHgdgGuesDlNJwhuXTJdXLmXn7u9n+p7+/1tfrC+sIZl04n7K5mepL88Nii7XhqyU5VUvn9yVJgGGWIgN3wttFTJkJHIy6jlCiKa5fa27hu1v2oWfkK8j99Ajn2o8CaJ4G1zwCjzkH3qTciIzkWBRV16sRBekpim8rWRHiKYyTapq7erZxiSW4kEUKd8VlQ645BTGxknfwghJBIgUKnHZG5MDF2myoNyyutUYsSTxBBj+YXORLBLLX9P311NT49aFNx0RMHNF/GVlJZ62lObkp4CMMyuymB8t6agx5xZp1Lox0dmW3jrHfBEWM3hdQx1Na7VJP/dScNwq/e2YBFW46o8pxAHKu1BZyh4xsxfchPXK51WKjMGNJpa9IrVFfvQqy5b57nq6pT/TdCj6TGC4keSbHYg0QciumHCabIEf7x5W784eOtnh6LgRlJGJCeBHeRDXPkeYI5EW+PAYbNNrZjB4wSJ3ECyo8AS/9qbBLBrUVNByC7fZYtDvby14Hhc4Chs4CeQ5q9nwywFa30wIebMH1ITyOUoq2IiCvcAeR+A+xfYQgILRT9hEogpTfgMN8j2ZmaMqNMUBw0FXxR5P29hFxkDPdy5YKi3mnsl+zP4fXG5cFVgNPsgZHHHXWuIXAGngLYvX/nkNgDB0Zdh9nzB+Ps+HV4YtA3hsDd/IHa3nIMxoeOCcB3m4GqkUaIhjhByZlAXHCv61EzcU3+H8nvvPxfkBMA8n8+M8lmlGyWynaw4evqEuACPQuLhAV1xsmjKsR7/q4SQggJLRQ67Ui8I0b1uOzML8eWI6WG0CkKnLjmj3njemPe2CzM35iHP32yHa/ecGKTTpB1fo6UpTVVHmdNXtNnia1BBHqhLyUyNU4XDhRXeYTPd2YQwfEDeuD0kVmw2TYoF0lKaHp39y/iSs2RNZm6dM0PfdKMM9L+UqS0myMCMM5hVyJNxEpxZZ1yl2RfrOjoaHkN5Pa+pCcb+1FU6e1yvLOqIdxAwiRkE9cIiMFJW/Jx3qTArsanm/NQ7azH2eN6N7xP3fsBM+8BTvmF4e6IyyNuj0RwN4vNTHJr4lLdzPdr876eh7HB7XLCIeUyuz41NkEGv4rgkW3QycagVx/nTsePV9TWq8GXr1w9yYgSl8eKTzHS5hzxTSfOSU/LodUNwmb/t8biuwU4YuIw15YAx/pqI8K8JYhQsgofuew51OjlOmIKGtmObGwQNVYyRgDHX22EDST3bDZa2gU7tnQ/GbjmN0DeJuDbfwDr38Ag527c6tgNbHwP2Ohzx/hUQ/SI+JFkQxFVUhZnsxubiCqbHYPyK/CgowT9aroB//cmnnBvRmJcHoa+WgbUNPTNNeLsRxmOEU7I/wlT6MSy5JAQQtoFCp0OCCQQoSPlazNHZFoS14JfcNw5Zxg+2XQEy3YX4csdBTh1eK+g+nPG9m3azbEmr2nGW4IIBKn5F7EmfUZSvuYROmZ/zuQB6aqE5rj+PZSzIgv9H05r3FAk5WDNlq41M0vniCl0tBiSfZPytQWbpHytsJHQ8QwL9YmW1ughokXm7YR9hRVqYS8lfJ/feZoqB5LjfmNlLr7dU4w1+48FFDrHKutw439WwelyY9HEPDxy0Tjv0reYWMMNkK2i0BAKdodyf9y2GBwsrUOfHsmwy+3EEZJFrq9j0AactbX46t3ncWqfGsTs+RzYt1zNQsLK541NRX1PBwadYrgmlUWoKDiM52N2IyO2HGkoRVpuOWy/8xPcYI81RI9K3Es1xI/+vnifISZ8nSsZktj3eCBnCtBvsvHzMgmSOGwGSsgcJvOyqgi2+lokwCJwZH8TZShtunnZo+F7WUQWSHrediM5T8902vNF8y+U7Hv2eKD3BGPrM9EQRkFGh3uipXUZWdYY4Ly/q7K2r999Enu2rsXY7tWY2KPWOM7yfKPHS3p+ZJOAjiYYIZv8Wsl/81XAZPX6Q5I+zNcl3iilS+1rCDwJ9ZBNyiNJ2GAzBXWlm44OIYS0FxQ6HSB0ZC6OTl7TpWstKf+RGOqTs91YctiGR+ZvwUlDM1RJXFv7c3T8tKzfdBuHr6MjDOzZIHRmmqJllZm4NnmgIS5mj85SQmdhAKEjQw2dbmOfewVTuubX0anyXkACmDo4XQmdb/cU4WbZOT/DQjNM58aXHlroWBydT7fkq8spg9KV6ybbpP494HTWK6GjAxv8sfZAiRI5wgdrD6n3/NkfHO8Rh14oV6DBGfhw7UHc+vpa3DlnOG45PXD5X7As3HREuVinjbAMbbHZUJbYD65p8xBzyh1G+deer4CdnwI7FxlBDbuXGJuJ7OHpftLJVSqeI6HB/ZDUOSkZky0QUorWfyqQc6JxKWJCRF0wOGtQV3IQSxd9hJNmn43YFCn1Sg5OfJizkAzhI7Hh241LOV4padOCRm0TgfTBbRKY4gAKUlLmRVI6yidej99sXIORjm5YcP2p3qV4IniU8MkDKgrMJD+zd0suzW3RpsPYfKgY0wb1wJTBmZi/D3hzez1OnDAWN557iiqf4zynyHF0ZPBxbAzfL0IIaQ8odNqZEeawTp281tLSNc2cvi6sLo5TjyP9NDJYtC2JaxqZsSNCKreo0ivFyYpv8tquoxWqXExur8WUCB3paxFnpbS6DqkJ3gvY/FJdRubw6gEKJHSklE76cawlZ/pMub6NII6OsGpvUaM+HV261qyjYxkaKo6UcMaoLK/bjjPdMSkLtPYqWVmbq+O5u+NgiTE/6dwnl+LxSyc2ejxf5HUTRLDdgraRX1aNm15drVyptffNCThHSTkuI+cZmyy2xUnYsQg4sNLoF0nqifk7a/D5ARemjx2O86aPw60f7MPSQ25MGDYAL/3oRNhk8V1bbizU1SZfl3pflyQDZqcYc5VauwCX0rjuOShN6m/MQmpJYpmImZwTjM2Ks9YQWiEWBZ4ZOt0bC+zBGcb/+71FlQ0pafL8Mn9JtoyhzT7+a3tW4jNnPrLGjcOUKf1xdNleLNm6CfG1WYabRSIqda1SCR06OoQQ0h5Q6HSAoyPsyi9XC2pxNoR+LShdE5JjgRtPHYQ/fbIDjy7chnPG9/YrGCpqnNhtJqQF4+jo1DQROhJc4O8D1zd5Tbs5MsNDCxFxhmQujwwX/WLbUZw7oY/XY+Sbs4N6dWs6oljEhzymiBxZMFoFoXXavPX1lR4cCR6QgARxX4ItXevhI3TkMVbuNY5t1ijv8fVSvhdvd6OqzoWdR8sx0hSwVtbuN8r5LpjUV/VWSYiEuFzXvfydmjF07QwzZc0PUt4oyOvXVtbvP6ZCGGST96y5QIpGUd8Wntz8FTbXl+KMcccjZkA2brtsLBb+/St8saMIb313AN8/IccQEsE2+4cT7RTzrUssrc6jpl9aImJsblTXuXDoWFWrgh10vLR2RhtcUM7SiUih445HPHt0CGk36uvrUVdnNgm3A/LYDocD1dXV6rkikbowPIbY2FjExAQ+MR4sFDrtjPScJMfFqEbur3Yc9Szmk+Nb/tJfNbU/Xv32gGrU/9fXe3HTaY0Ts7YeKVUn52VWTbBzT8b1TVMlW1MG+j8bPNBH6Oj+nBPMsjWNuDr/+GK3SunyFTp6cdZUEIEgZ7j7dE9QkddynN5Cp8oTLW29vZSZSXKcuCFWoVNoiqueAUrXfB0didiW0jMJOZABj1akVDCnG7CzFCr4wFfoSDnf2v2GozMxJ02VLb12w4n43Ueb8cryfXh04XZcM31gwCAJLXRk8StDZQO6MEGw0XT0hF1Hy4MTOn6QIILteWVes5UkSfDns4fjkY+34qH/bcbMkZmcrxPA0WlUuqYCFezISADyqgxR2xqhI46dtddN96z5K/ck4YvNFDrViEMyHR1CQo58Lh85cgQlJSXt/jzZ2dnYv39/s2FR4Yo7TI8hLS1N7Vdb9olCp52Rhfjw7BSsyS1Ri3E9Q6c1xMfG4OdzhuOON9fh6SU7cdkJOR5XojX9OZobThmkymzOHu8tTjQSryyI8JDFr05ckyACK7NHGULn8635jcrIdKpbZjOOjj5DLULHd+Gmz5T7prpJzLQSOrsLceOpQxr36ARwdGROjlXoBCpb0/RPdmNnqQ3rD5Q0micjjpiU88XF2D3CQpype88ZjddW5KK8xqleP38LW3l+ua8gIjVoFyYA4mxp2uIQSY+RCD9JthPxqbn+5MF4d/VBbMsrw5Jt+fheNM7WCaJHx+o8WslMcCOvyqZE6CnNBIv4IuVu2qnUAlMHeMhQV/n/2VRpKAm/OTpSupbNHh1CQo4WOZmZmUhKanp2YVtwuVwoLy9Ht27dYA9hgFBH4gqzYxDhVVlZifx8o2+6d29zVl0roNDpAEaaQkdKulqauObLBRP74vmv9mDL4VI8+flOtZD2t8gd24KFsiSDXXpC/4A/l3KzbvEOtVgX10I7O5K0ZkXcFHFJZMG1Yk8RZgzN8DvksDn8BRL4Dgu1MnWQIbhkuKrcR9+/oUcnvlmhI8JMFu3C7NHeZWua/t2MoAE9KNWKdnNG9UlVseIaEXtS1if9OiIc/Akd7eZodhe03oURrIEJsphu6+OM7dvd6wNC3K3TRvRSQmfN/hIKHQvSv6Xdy4BCR349i1snQiU4Q0oS5e3QJZlSupkUF4PK2nr1f0TKLElkla45wmBhQUhXQsqvtMjp2bP5+YNtFQm1tbVISEgIC5HQVY4hMdHs2c7PV+9ja8vYwuNoujgjzAb/shpnq4IIfB2iu88aqb5+ZfleT/CA7wyd0S1wdJpDFrna1XnbnDEjfT3dk7wbwmUBfIbZ26Ldq5aWrglaqEhDv0YcD5nl468kaFTvVDUzSITYeU8uxXdmn412dHSJWiChI4/75fajqn9KrpuY4y3gfIWOiMwaZ71foTMpxzueW9ABDyIM/NFI6LTBhZGyJhlOGwqho0XzuL6Nf5cm9TeOc7Xp7hGDo+U1kOA9CYIIlPaXmej2CNoWP775/yg9Kc7jmMr/z6bSCkmY4ildk7lgdHQICSW6J0ecHBK56PevLT1WFDodwMje3mfnJeWsLUi5y6xRWaird6uGd2miF2TxrXsqgpmh0xIkYlqYv+GwupwcoJ9n9uhsdSmR2rqnpqWla3399Bzor/WwUF+B9er1U5XgkbKey5//Bq+vyFUzcJpydOQsuH6sN7/bry5l1lGg6O70eKgyLnndtx72Fi3W/hxfRpiBFNvN5L1AQkfK3kIlTqQvTIsmKXdqi6PjT+hoN09+30RgEu/yysyUeCNRzQ9ZWui0QtAGcka1y+lv0C4J/9I1OjqEtA/h1G9COuf941/XDkxe0+Skt306+V++N17V5u8rrMQv3lqnSrt25JWrnoq0pFhP3X6o0MlrUh4jTPYZzqk5eVgGBvRMUiLj+/9Y7onTbmvpWkN/jv9yIHHJ3rlpGs4e11sJkbve3eBJuAvUoyP/gbTbs3hL02Vrxu0bSgKlT0cjCXHaSfMrdExHR0eM+yIpbsKMoT3b7OhsOGDsx+mjspSrUFVX7+kZaQneormx0MlMTVC/Y6Kh1psij1iCCAL8ngqZ5o+kzKyy1tmmxLVgBu2SMCU2EUfd3XHMnQwHe3QIIaRdoNDpANKS4lQKWqgcHf2YT195nHIBZEjnC1/t8ZzNl/k5oT6L4Tv00jeIQCON0P+94UQldvYXVeGy577BvsKKhtK1IISOddEmAk44XNq00NG9Rk9eMUkN3dTIYt93po+/8jURiPJanjys6eZw7W5Y+3SklE3Ejrg9ctyBHB0RMNIL5ItEjwtzxhhu2O6j5Z7jbq0LI4JL70trHCLpJxLBKKI5UBS6p3wtl+VrwURLW6Pi5XelNaLWN3FNw9K1yMM1815MrXkaz9efwzk6hJB2YeDAgXj88ccRzfCvawcPDhX9YR142RZkjs295xphBH9YsBVvmOVXY0PYn6OxNjiLWGnKlRKh8saPp6m5OlJKc/Ezy9X8GaFXSvOzS/TrI5HcpVXGGe/D5gKuKaEjiMC75fRheP6qyUhJcOCEgekBS4isQkeYNqRns7Hf482SQKvQ0WVr8n74E5g6Yry23oW9ZpCDRs7o63KjM0YaZXNy3NY+m9b21UgIglVI+SLi84f//NZTjhiobC2QaNblaxK0EQiZS+TbR9aVOWK+b/6ipf05pC0VoYEcHc7SiTykpNQFm+eEDCGECKeddhpuu+22kDzWypUr8eMf/xjRDIVOB5ev9U5NaNRj0hZ+MLU/zp/YRyUx6QVnWxK7ghE6kwf2aNYxksQpETsSWqB7ZRJi3Mp1aQ5xhXRJmRYBntK1IEWizPRZ+etZ+M/1U5u8nVXo+A4J9YfufdqRX+YpO2qqP0cQoTUsQCCBPqMv+yHlYBKqYFzfukZ1KVOTt0ZcvcGm0NEDZH15Z/UBfLWjAPe8t6FRn40nvc9P2ZqvoyPJa/4cqNzCSuXoXf7cN43CGzp6Qfncl7vwf+sOtftzHfEz66mp/08td3T8O6OcpRN51Fl652S+EiGEBIN83jqdwZU99+rVK+oDGfjXtYOQhacwqFdoo19FcDx84Tg15FLT1OK0LaVyUsYkHB+gbM0XOessQzMlJEA9RgsG0fuW4sggzWAWkL6CKVCwgKZHUsNOSV9Lc8iZeilDlDWKnlnUnNCxCl3fQAIdRDDUFCVDzN+PXQHESTCDQsUtEGfK81gBRNMqMzGtpLIOr36zL+ggAo3MahLRLvHcMvfIl/9tOKQEuPRKbfATyd1RfLThMB6evxW3v7HW44h01gwdzSAzxTCQCG11j86xhnJPEv5R5BodREIIiW6uueYafPHFF/jb3/6m1neyvfTSS+ry448/xvHHH4/4+HgsXboUu3btwvnnn4+srCw1/+aEE07Ap59+2mTpms1mwwsvvIALL7xQCaBhw4bhww8/DDqy+7rrrsOgQYNU9POIESPUfvry4osvYsyYMWo/Zf7NLbfc4vmZRH7/5Cc/UfssUdZjx47F//73P7Qn/OvaQZw1tjd+MXcEfnO299ybUCCL2md+cJwq1ZKSMp2QFmokkUxKsIJxPjSSePbaDVPxg6k5OLd/4/6UQHjOUJsCJ5jeh9agnaPRvVODDnAY3y/NE0hQUlnrmSvUlNAZHiCQQIuQIZnGeza4mXIzcVpe+Gq31yLJ87MD3uJkiCl+d+U3XkzLYniNpbfm+a92q2GTgrgv0qNjfSx/iMjRP7c+lsZaEvftHiPyu6OR3qm/LNzm6cN6d7URj95e5LWwdK2lzl1BAEdHC6vqOpdn+CwJb+T3UcMwAkI6aAhlrbNdtqra+iZ/HuwJKBEO06ZNww033IDDhw+rLSfHmFV311134Q9/+AO2bNmC8ePHqwGf8+bNw+LFi7FmzRqceeaZOPfcc5Gbm9vkczzwwAP4/ve/j/Xr16v7X3nllSgqKgpq1k6/fv3w1ltvYfPmzbjvvvtwzz334M033/Tc5plnnsHNN9+syuU2bNigRNTQoUM99z/rrLPw9ddf4z//+Y96DDme1s7HCRYODO0gZFF480zjzW4Phmam4ItfzFQfmM25GK3lz5eMx+8vHBtU+ZmvG/Tbc0Zh/vw9Qd+nYZZOldew0FD1N2nOHJuN99cexM9OD/69mdCvu5oTtO7AMQw1xYSUIslxBsITMZ3n39HR/TSeBXCAM/13vrVOiSX5fbpq2sCAAz7VY2Z087gMUpoWbzmtkVtUqaK45UyyuAPyOr+2IhfXzhiE7UfKVRCBDKIMFESgkblB4gxJIMFFx/XzXC+9SNrxEmSA7M0z0eG8vjJXJRNKOZ98zryxcj9+fMrgdokcld/TYAW5Ll0TkSz3C3Z/AqUXypBaiV6XMlFxQa0lmSQ8sZ6sYI8OIe2PpJCOvu+TTnnuzQ/ODWrt1L17d8TFxSm3JTvbCCjaunWrunzwwQcxe/Zsz23T09MxYcIEz/cPPfQQ3nvvPSUurC6KP9fo8ssvV18//PDD+Pvf/44VK1Zg+vTpaIrY2FglkjTi7CxfvlwJHRFOwu9+9zv8/Oc/x6233uq5nThNgrhN8jwi1IYPN0KjBg8ejPaGjk4XQhY3TSWMtRWpI2+pyGktDclr1V7DQoMZONoSpHdm8c9Pw1njegd9H6ujE0zZmlXo7Cuq9IoU9pSumYJJuzD+zvRLdLF2hF5etrfRGSLfAZ8y0FUWv8Ien14QXbY2pm8qfjpziPr6H1/sVm5OMEEEmuMG+A8kkHIx6/soQ1z9uVDtiYi7v326Q31915kj1dwkEZAr97ZPSpyU6MkHaTCla/3TE9XiVuLag43/lt8b3Usl/VyB5k9xlk5k9ejI7wFnfRBCmmPy5Mle34ujc+edd2LUqFFIS0tT5WsiIppzdMaPH+/5Ojk5GampqcjPN0ZsNMdTTz2lyuek90ee77nnnvM8nzzGoUOHcMYZZ/i979q1a5UjpEVOR0FHh4Ql1h4dPXhUFu1y5rqzGd/PEBLiFHyx/WhQQkf2XcrkCitqlbgRsSQL/72FFV5CRzs6sliVUjLpM9Is3VHg+XrX0Qos3VngicMulDP5x8wgAku5mSTfyVl+KZEbmdXQkKgjoY/v3wOXHN8Pf1+8Qy2431l1sJEz1BQ6eU0EmCzEtRDWZWs3nTYEf1qwVYmAzYdLPSKxI3j+y93q9Rb35EcnDVKN/5JMKC7PlEGN+8wkHe4372/EjacOwVwz6rs1M3TECbO+b/6QOGEJnhDhJaWFvbsnBt2fkxgb4xkI6/t/RlxGBhJEBs56U+iwbI2QDkH+doqzEmqkJKustAwpqSmwBxj+K8/dVkSUWBGRs2jRIvzlL39R5WHSN3PJJZegtra2WWfGipxokWNojtdff10956OPPqrK61JSUvDnP/8Z3377rfq5PH9TNPfz9oKODgl/oWNG5rYkiKA9kRI1nY6mnYzmhI7V1dH9L+LuSImY/AHsYy50xZWThbKYNbr3R/PVjqNefzBfXtYQIKDFiSzqu1kisj0R0z4O0ap9xn4fP6CHEo8/OcVwdZ5estPjUjXVn6MR50LeFwkdWLff2AfZbxmgKiWU88b1VhHfwre7O65PR0SB9B0J0hsnwuKyKTkeEXasqq5RL4+EFcj7qV2gltLcUFtfRIQKuwu83xtx6uQ98E2q88yiSo336wBwlk5k4TQXFo4ACyNCSGiRv5tyMq49tsS4mCZ/3hLXVkrXpPG/OaTXRcrQJFhg3LhxqtRt7969bXyVmn4+KW/76U9/ikmTJilxJYEIGhE+En4gPUOBnKQDBw5g+/bt6Ej4F5aEJTqMQM6S7y+uDKocqDNcHUH6ZXSyXFPoQAItdHTggAQR6Fk/8sdQp6VZo4dl8bt0Z6H6+tdnj1KXi7fmYX9Rpd+yNY2/5LWyaie2HSn1Kj27fEp/5TgdKK5SA1D9PVYgPPN09hd7uTnTh/RUwm3q4PQODyR44rMdqixMZhudNTbbI0ZHZKWohv0P1x70uv0/vtiF7XnGayTOk35dWyN0mgsi0Hjiv33KCh//dAcueOpr/PFjI0ShUX+OWY7oC2fpRBZ1TsPRiaWjQwixIGJBXBIRLQUFBQHdFklMe/fdd1VJ2Lp163DFFVcE5cy0Fnm+7777Dp988okSK/fee6+a02Pl/vvvV46P9P3s2LEDq1evxhNPPKF+duqpp+KUU07BxRdfrJyoPXv2qCS5BQsWoD2h0CFhSUZyvGqUlzJ27TD0CSOhM8FSgiXR4cHMRvI4OmYgwU6duGYueDWe5DWLOJHSMClBEzfne5P74eRhGcr1+bcZC+0pN/MZFtvQ89OwmF5/8Jh6XaV/Ri/K5WzU9Sc3NAWKq9TUUFh/83RWmy7RR+sNoXO22fc0ZVBPz/BQmWnji/QjycI+VHNuxFH677e5nt4cfSZNLi89wXB1Xl9pDNfVr/MTn+1UX+sI9U82HWl9tHSwQsfP0FARwU99buzL26v2e5LwfB0df7BHJ7KoMxck4jYSQohGysMkiWz06NGqFyZQz81jjz2GHj16KJdF0tbmzp2L4447rt326yc/+QkuuugiXHrppZg6dSoKCwuVu2Pl6quvVnHWTz/9tIqYPuecc5Tg0bzzzjsqnEDCEOT4fvnLXwblXrUF9uiQsEQcjt5pCaoP5juzeTw7iD6GznB0gilb81e65jtDp1FJk2UBrMvWxB2RUrOrpw1Uwz4lRez2WcM9CWe+fTU6eU16QaS8zFpuJ2VrVn5wYn88+8UuVdYlg1GDtdonmY7O2v3FSmSIIyJla3PMPhcRghIEII8rIs/X/frb4h1KzD62aDvOGd+7TY3Z4nxJT5BE9542ohemDTFElubCSX3xh4+3qtI6ccEkVvzudzegtt6FU4f3Uvd54P82Y+GmPC/h1xKhkxV06Zq3CBUReNe76z2xw9LX9PnWfE9QRn5ZdZOOju7z0T1tJEJ6dJi4RgixIM36kmZmRUrU/Dk/n332mdd1Eu1sZa9PKZu/mGuZbSNOUGlpQ1KqP2Quzr/+9S+1WXnkkUcaCSLZ/CFJcTJnpyPhqSQStui+FX2GWpezhQMiKPR6PFiho4e6SglScUWtp3RNBxFotMNjjZgWUSOcNDRDXc4cmakcFxEP/1q2x/MaSYqalb49EpXbJD0o+jZr9vsXOikJsfjpaUavjiz6g0VEkbhvElf97JJdXmVr+oy1fi6JmbYii3dd6iYiSbtdrRY5n2zDxxuPqPfmV2eObHSbHslxKlJckFACCSeQfRKn7HcXjPWIs5X7ipSD1hLyWjjrSZcVyqwomcHw6rf7lAiVHquLJvVVP3tn9UE/jk5Ck6Vr8vsl7zcJb7SgZRgBIYS0HxQ6JGzxnZkT6mGhbR3SesbILNXXMsMUH80hQkLHLcuCXpLT/Asds6Qpv1wt3qV8SQsEnbImjslVJxpzdCQxTQcR+MaLy+10iZQICVlbrTFDA3RvjRWZMfPpHafgupOCdzPEYRrdxxBYb64ySsLEmbEy1Uw58xU6UmImgQyajze0vGRM89dPd+AZU2g9eN6YgH1Tl5nlax+sOYSH529RX/98znDkpCep90d6k+Sk16eb81pXutY9uAh0a/DEN7sL8ccFRk/OL88codLqhCXb8lFUURtUj478LoqolcdjIEH4U2fGrTOMgBASDtx+++0qalpio323G2+8EZEK/8KSsEX3HGiCieDtSP7xw+PxzT1neGbVBMNIs3xNStFkJooIkQE9vSMj+6cnq+srauvV4lZK92SOUGZKPIZnNYii70/OQUKsXTXXNxUH3eAQVSK/yggjEAdjZG9jX6xI2ZgMn23p0FktmmSRrcrWRnvHM+s+nW/3FHqsc3EdXjV7aWaOMATcgo2tEzpPLN7hEXz3njMaP/QZpmrlxME9VWpeWY1TvRZShiiDUjVzx2Q12acjoQPST+Tbb6TjpYMNI5DXWpcp/uLtder34bj+afjB1AFqvpMILjnrr3uXtKPTK0CPjpR7anEnrzOJDEeHYQSEkHDgnnvuUeEBEm7gu8mw0kiFQodEjKOTFeSZ8o5CFvQtbSQebgod3bA/ID2pUZCBfK/jq6VR/audRn/OScMyvPpXZCCo9JxoxvmUrWn0YlocpD1lxv0n5HQPaRP0cQMayvfE4ZISMSvyfHJcUt6mS/I+3nhYLd5FwP35exNUr4I4Xf6GpTaFuDiPLjLiKu8+aySuO6lBtAQSBDqUQN7DP1w03kvY6Rk6X+8sRFl14xjqH/7zW/zstTV49suGWE2JgpZja6kgH2z2UMl95fgfuWi8J4FPv7fvrjkYlKMjnDEyU11+uiW44W+k8x0dhhEQQsKBXr16qchof1tmpvHZEonwLyyJCKGT0S0uLIaFthXt6OwtrPRKRQucyFXhGRQqSWu+XD29wblo3tFpEDr+ytbagg4kEM4e13jYprx3k8xeJl2+9tIyo0nyyqkDlCumgwOkx8YfO/LL8f5eO+77cDPufGudEhtXv7gCf1yw1TMv5yenGiVfzSGuyaxRWXjw/DGesjuNlBLK6y8BBZ9vM0Sm5oWlu9V+CE8s3ukpEcsvNUSIiLkeZnJbMGgRKsigUh1YIZw3sY8SYOv2l2BHXpkaCttU6ppwuil05HfGmthGwg8ODCWEkPaHQodEhNAJt7K11qJn6Wh8+3M0WgCt3FOkEsIEf71AI7NTlYMhpV++4QK+QmePRegEum1rkehvKbuSPirtiPii+3S+3V2I9QdKVOO9lO1cMbW/uv6ssb0Dlq/J2e9bXluHzw/b8drKA3h71QFV0vXFdkOI3HrGMNw8c2jQ+ytu2AtXT1YiyxdxzeaagQXW8jWZraPL40SYVdXV43cfbfYpW/M/zLO59D7pr7rldO/9l+fQoRAvfLVH9VeJ2dMzObDQkYQ72QfZN+n7IZHQo0OhQwgh7QXjpUnYYk1ZC6dhoW1BzuDLWXod9ewbLe3r6Eh5l3aCMlP8vwbSk9LccwpGaZWtkQMTCmRx/+ZPpqm+g4RY/87b1ME9gc92qsGhujzrnPF90CvFWLjPGZOF37y/Qc0EElEh4QCa11bkKkcq2eHGdacMRWJcLOIddsTH2pVImCaPHUJErElJ3JKt+coZkef67YebVD/UiYPTcd85Y3Duk0sxf8MR1W8l6XetCcyQFL3nr5qsSvv8vW5SvvbZ1ny8Z5av9ewW32T/lLwPp4/MUq+X3O+0EZFbbhA9PTo830gIIe0F/8KSsCUpzuEpA+rdRYSOlHBpEdOUo6NnrOhEMn9lay1JiLMuwAf1TPJEP4cSR4w9oMjRg0Xl7PXhY9X4YO2hRqV34mCcMDC9kZMiIuKvZg/OvBwXfjZziEol+9FJg5QjM32Id+9SKBjft7t6zSQQYtmuAnyyKU8JB3GgfnfBOFXu9sMTDTdIBND+oqoWBRFoZL9nj84KKGLlZynxDlVG11x/jmbWKEPcLN6S73dmAgkPOEeHEELaHwodEhHla12ldM0aSNBUj46OmNboWOnWMiQz2UtwdJZwHWeWaomjNSEnrdEMorPMkjFrn85Tn+9EcWUdhvZKxrSsjlm4i+MkDpPw7uqDeOD/Nqmvf3LKEI84vX32cCXOZODn81/tbpcIdBGO88yBoc3152hE+IkDJXOT2jKXiLQvTpdZusYeHUIIaTcodEhYIxG7gm/DeCQzwuzTkUWxDIdsasaKbnCfYva3tBbdpyNIL01nYT2Oa6Y37o850+zTWbWvWPW97CuswEtfG6EFd581Ah25JtS9Rv9bf1i5UJKEZ+2jkfdHUt4EPeumPUosLzquIVkvGEcnMS7G088lrg4JT2pNR4ela4SQUDJw4EA8/vjjnb0bYQP/wpKw5v7zxmD+/zsZp7ShdCvcmDywR7POinXGygkDezRZEtZioePjonQkM4YY76M4IVanQiNCQb8uUr4miWpStnXK8F4d/jsgokyLTeGB88c0eh9EhEy2BDu0h9CRcj49aDYYR8eavibldiQ8cep4aQ4MJYSQdoN/YUlYIwtLcXNC3YPRmUjj/Ds3TccfLh7f5O10WZfEILcVLXQSY9yNyuI6Euk1+v2FY/HiNZMDxoXr8rV/fLFbNftLC8Ov543q4D01zrTPGW289vPGZWOmn8Z++b188Pyxah/bq8RSyuh+dvpQNRz21OGZLRI6q3OLPW4TCc8wApauEUJI+0GhQ0gHI4tjiXe2ugX+kB4QSeS6elpDw35rkaSwK6b0w0WDXJ7Es846dgkQGN8vsKukY6alx0S4bEp/r/kyHcldZ43E/eeOxh+bEKUixP90yQQV861nBYUaeQ22PHhm0CWM0ts2uncqJItgyTa6OuEI5+gQQnx57rnn0KdPH7jMHj7N+eefjx/96EfYtWuX+jorKwvdunXDCSecgE8//bTVz/fYY49h3LhxSElJwZgxY3DzzTejvNx7aPfXX3+N0047DUlJSejRowfmzp2L4uJi9TPZzz/96U9qqGh8fDz69++P3//+9wgnKHQICVNSE2JV6lYohIkkoj1w7mhM6RX+KVwSKy3zYATpYbp91vBO2xeJc75mxiCkJDQtSi85vp+K+W5PEdlSV/MMS/oaCec5OvwYJqRDkDM/tRXts9VVNv3zIBMwv/e976GwsBCff/6557qioiIsWLAAV155pRIh8+bNw+LFi7FmzRqceeaZOPfcc5Gbm9uql8Rut+Pvf/87NmzYgGeeeUY97y9/+UvPz9euXYszzjgDo0ePxvLly7F06VL1fPX1xkDqu+++G3/4wx9w7733YvPmzfjvf/+rRFg4wTk6hJCwQ1yfe97bgDvnDPfM2SEtQ8rXnvhsJ77cfhS1TpcKtejqPPXUU/jzn/+MI0eOYMKECXjiiScwZcqUgLd/66231Af03r17MWzYMPzxj39Ui4iOnaNDR4eQDkHEyMN9Qv6w8pe1WS//nkNAXPNl4+KYnHXWWUowiMAQ3n77bWRkZGDmzJlKmMjfNs1DDz2E9957Dx9++CFuueWWFu/7bbfd5nFm0tPT8eCDD+KnP/0pnn76aXW9uDWTJ0/2fC+I8yOUlZXhb3/7G5588klcffXV6rohQ4bgpJNOQjjR9T/5CCERx+VTcrD2vtnKTSGtY0K/NPRMjkNZjRPf7S1CV+eNN97AHXfcgd/+9rdYvXq1WgxIiUV+vn9Ha9myZbj88stx3XXXqTOjF1xwgdo2btzYoY4OU9cIIVbEuXnnnXdQU1Ojvn/11Vdx2WWXKZEjjs6dd96JUaNGIS0tTZWvbdmypdWOzqeffqoEVU5OjtpEsIijVFlZ6eXo+EOeV/Yx0M8j2tGJpLNmhJDIQ8q00pJCP9Q0mpAyupkjM/H2qgNYvDUf083I6a6K1JrfcMMNuPbaa9X3zz77LD766CO8+OKLuOuuuxrdXs5EStnHL37xC8+Z0UWLFqmzk3Lf9oYDQwnpYGKTDGclxIgbUlpWhtSUFCVGAj53kEhpmAx7lr9f0oPz1Vdf4a9//av6mYgc+Tv1l7/8RfXFJCYm4pJLLkFtbctDZ/bu3YtzzjkHN910k/r7FxcXp4SN/B2Vx5OeHHn8QDT1s4gWOvqsmXwQTJ06VWV1y1mzbdu2ITMzM+BZs0ceeUS9oGLHyVkzOeM2duzYUB0HIYQQH2aNMoSOxExLD1FXRT6UV61aperFNbLgmDVrlqor94dcL59lVuSz7P333/d7ezlzqc+wCqWlpeqyrq5ObS2lxuk09hPuVt0/HND7Han73xWOIdL3v72OQR5LxIKIEK/GfkfoF+fyPIithzs2Ca5AvZRymyD7dERwXHjhhfjPf/6DHTt2YMSIEZg4caI6DgkGENdFAgkEcXhEsOhjte6Tb6CBLytXrlS3EeNCTi5KKdr8+fPVz/TrJkEF0g8kTrkvUqYmYkeE1/XXX4/2QPZBjkXez5gY76TWYH9fHF39rBkhhEQrJw3rpXpA9hRUYG9BBQZmdF60eHtSUFCgmmN9m2Dl+61bt/q9j1Qk+Lu9XO8POVn3wAMPNLp+4cKF6sxnS9m7T8782rFv727Mn78LkYx8pkc6kX4Mkb7/oT4Gh8OB7OxsJQRa43a0BhEKoUIMASlXk1La73//+54TKzIMVHp2pF9HePjhh5UYkGPUt5Hvq6urPd8HQl4fEQviDsk6/ZtvvsE//vEPz7HIySLp+5kxY4Zn3S8iTBwm2b+ePXvi1ltvxa9+9Sv1nP+/vTuBjarqAjh+2EpZSit7KVBAEBGRL6CY0liIhQIS2QxWRFlENgFFlKCoZXGBYHAjRhOJoBEBQZbopxGElqICpoqgoMQSEJCyGaAsZSl9X87V6ddlBmmZdt59/f+Ssc7Mm3LPu9N33l3efTr4ocdiPeY+/PDDEgwaV05OjqSnp0vuP51DPr7pdUFt6JRHrxkAIDh01brXk/8j7aLrSGy9kp+M4/807xXMZXoSoXPak5KSpE6dv1cJLInWWaflv6nfSb/EeLmxUck/7wZ6kqQnpz179pRq1a6+MqFb2R6D7eUvqxj0RP/gwYPmGpbw8ODfyLkgHXHQhoEu0Ryse/7pDChdHEBHdEaMGJF/jNHBAx090fNoXaBAV0jThoA2QHzb6Hm5xvxvx6X4+HiZP3++aejoIgRdu3Y1DSf99zQW/XynTp3Mim/PP/+8OdfXERy9VEUbPfq+Dl7UqlXLrLx2+PBhiY6OlrFjx5bqmBioHvXfTEhIKFaP/9aQK1VDpzx6zcpiioDtQ7u2l98LMdhefi/EYHv5QxVDr3YNzM+ivWHXyob9rQlfpzUcPXq00Ov6XHst/dHXS7K93iNCH0XpiVlpTs5uio6UzCjHNHJsPUG93n3gJrbHYHv5gx2Dnqtqo0NP+gNeNxMkvilivn8vGPT3aMOhqFatWsnGjRsLvVZ0tTWdynatpkyZYh7mOqPsbNNA8a2g5qOjRzplLlA5tRGkj7Kgv1/3q7/vxrV+V1y5vHSwpwh4ZWjX9vJ7IQbby++FGGwvv20xXOv0gFDS3szOnTubueQ6pUJp4tbngZZcjYuLM+/7llf11Yu+DgDwhqpu6zUriykCtg/t2l5+L8Rge/m9EIPt5bc1hmudHhBqmjO0J1Lv+aBTK3ShnHPnzuVfTzps2DCJiYkxHWlK55Z369bNTN3o27evLFu2TDIyMsydyQHAdros9dixY/2+FxsbK7t27ZKKoKobe82CPUUgWJ8PNdvL74UYbC+/F2Kwvfy2xWBLOZOTk+X48eOSkpJipkbrKkU6t9w3dVrvM1FwWonOR9dVQHXKxfTp082tD/TaUVYDBeAF/fr1MwsE2HxcD4YST12j1wwA4Eba4Rao0y0tLa3Ya4MHDzYPAPAaXVAgIiJCKroSN3ToNQMAAADgdqVajIBeMwAAALiZuZknKnT9le2aewAAAEA58l2DYsOqkQjMV3/Xc02RK5eXBgAAAEpDVwiOioqSY8eOmed6a5Jg3cyzKF2U69KlS+bmlmV9z56ykueyGHQkRxs5Wn9aj1qfpUVDBwAAAJ7iu42Jr7FTliflOTk5UqNGjTJrTJU1x6UxaCPnarejuRY0dAAAAOApesIeHR0tDRs2NPcwKyv6u9PT0yUhIcHaZZsvuzAGLcf1jOT40NABAACAJ+nJcjBOmK/2+3NzcyU8PNw1jYSKGEMgoZ+IBwAAAABBRkMHAAAAgOfQ0AEAAADgOVVtumFQdnZ2qS+y0mXq9PM2zj20vfxeiMH28nshBtvLb2sMvuMuN94rrKLnJUUMoWd7+b0Qg+3l93pusqKhc+bMGfOzWbNmoS4KAFRIehyOjIwMdTFcg7wEAO7PTZUcC7rp9EZGhw8floiIiFKt762tPk1GBw8elDp16ohtbC+/F2KwvfxeiMH28tsag6YITSRNmjRxxY3k3KKi5yVFDKFne/m9EIPt5fd6brJiREcDaNq06XX/Hq08WyrQi+X3Qgy2l98LMdhefhtjYCSnOPLS/xFD6Nlefi/EYHv5vZqb6J4DAAAA4Dk0dAAAAAB4ToVo6FSvXl1mzJhhftrI9vJ7IQbby++FGGwvv1diQHB44btADKFne/m9EIPt5fdKDFYvRgAAAAAAJVEhRnQAAAAAVCw0dAAAAAB4Dg0dAAAAAJ5ToRo6elO3NWvWiK1sL39R+/fvNzH99NNPYisvxJCWlmZiOHXqlNjK5hhsLjuCw/Zju+3l9+Jx3Qsx2H5spPzu4LmGzttvvy0tWrSQ8PBwufPOO+X7778XW8ycOdN8qQo+br75ZnGr9PR0uffee81daf0lOl3nIiUlRaKjo6VGjRrSo0cP+f3338WmGEaMGFGsTnr37i1uMWfOHLnjjjvM3dkbNmwoAwYMkD179hTa5sKFCzJhwgSpV6+e1K5dW+677z45evSo2BRD9+7di9XDuHHjxA3eeecdue222/JvtBYXFydffvmlNfsf5YPcVH7ITaFne26yPS8pcpMHGzrLly+XKVOmmCXyfvzxR+nYsaP06tVLjh07JrZo3769ZGVl5T+++eYbcatz586ZfawJ3J958+bJW2+9Je+++65s27ZNatWqZepD/7hsiUFp8ihYJ0uXLhW32LRpkzlQbd26VdavXy+XL1+WpKQkE5fPk08+KZ999pmsWLHCbH/48GEZNGiQ2BSDGj16dKF60O+XGzRt2lTmzp0rP/zwg2RkZMjdd98t/fv3l127dlmx/1H2yE3li9wUerbnJtvzkiI3/cPxkC5dujgTJkzIf37lyhWnSZMmzpw5c8xzDXf16tX576ekpDiNGzd2duzY4bjBjBkznI4dOwZ8383lL1q2vLw8U7ZXX301/7VTp0451atXd5YuXWqe79u3z3xu+/bt5nlubq4zcuRIp23bts4ff/wR8hjU8OHDnf79+wf8jNtiOHbsmCnPpk2b8vd5tWrVnBUrVuRv8+uvv5pttmzZYp6npqaa5ydPnjTPz5075/Tu3dvp2rVr/muhjEF169bNeeKJJwJ+xm0x3HDDDc7ChQut3P8IPnJT6JCb3BGD7bnJC3mpouYmz4zoXLp0ybRadQjap3Llyub5li1bCm2rx41JkybJhx9+KJs3bzZDe26hw+c6VN2qVSsZOnSoHDhwoNg2bi6/z759++TIkSOF6iMyMtJM2ShaH+rixYsyePBgM59YY2revLm4aZ6qDl23bdtWxo8fL3/99Zff7dwQw+nTp83PunXrmp/6N6E9UQXrQaecaNn81YPOxe3Zs6fk5eWZXqyoqCgJdQw+S5Yskfr168utt94qzz77rJw/f97v50MZw5UrV2TZsmWm10+nCdi4/xFc5CZ3ITeRmypaXqrouamqeMSJEydMRTZq1KjQ6/r8t99+y3+em5srDz30kGzfvt0MvcfExIhb6IF28eLF5qClQ6CzZs2Su+66S3755RczT9Tt5S9IE4nyVx++93zOnj0rffv2NQfj1NRUk3TcQqcG6FBuy5YtZe/evTJ9+nTp06ePORBUqVLFVTHoAWjy5MkSHx9vDrpK93VYWFixg5K/etDnycnJ0qZNG/n444/N59wQg3rwwQclNjbWnGjt3LlTpk2bZuZLr1q1yhUx/PzzzyZ56NQXneu8evVqueWWW8yJhU37H8FHbnIXchO5KRjltyEvqZ/JTd5p6FwrnZNYvXp1M+9SW+FuogcpH+0J0+Sif0SffPKJjBo1yvXlL60hQ4aYuaQbN240F4a6yQMPPJD//x06dDD1cuONN5qetMTERFfFoPOJ9cSjtHPntbemS5cu5nqCgonSDTGMGTOmUD3oRcS6/zXBa32EOgY9AdTEob1+K1eulOHDh5s5z7btf4SOm4/t5CZyU0XOTbbmJdWW3OSdxQj0wKoVUHTFCH3euHHjQhX2559/yldffSVupy3tm266STIzM60rv2+f/1t9qHvuucf0hvgbLnUbnbah37WCdeKGGCZOnCiff/656bHTpOaj+1qnzhRdHtJfPWivn670s3v3bnFTDP7oiZYqWg+hikF7uFq3bi2dO3c2q/XoRcRvvvmmVfsfZYPc5C7kpvJle26yOS+pMHKTdxo6WplakRs2bCg03KjPddjOp1+/fmbo7dFHHzXzFd1Mh5y1Z0B7CWwrvw6n6x9LwfrIzs42K9wUrA+lc4t1ZRCNraQ9DeXt0KFDZh50wToJZQw6J14PxDocrT12ut8L0r+JatWqFaoHHVrX+fVF60HLr7092iNVnge0f4vBH9+9IYrWQ6hiKEqPPTpVxIb9j7JFbnIXclP5sD03eTEvVdjc5HjIsmXLzMopixcvdnbv3u2MGTPGiYqKco4cOVJs5RJdaSI8PLzQihOh9tRTTzlpaWlmtZRvv/3W6dGjh1O/fn2z2ocby3/mzBmzoos+tGyvvfaa+X/fii5z5841+3/t2rXOzp07zQoxLVu2dHJycvyuCvP66687tWvXdjZv3uyKGPS9p59+2qxAomX9+uuvnU6dOjlt2rRxLly44IoYxo8f70RGRprvTVZWVv7j/Pnz+duMGzfOad68ubNx40YnIyPDiYuLMw+foiurTJ482WnUqJFZgcUNMWRmZjqzZ882Zdf9rd+nVq1aOQkJCa6I4ZlnnjEr8WjZ9HuuzytVquSsW7fOiv2PskduKl/kptDHYHtusj0vKXLT3zzV0FELFiwwFRcWFmaW9Ny6dWvAJRqXL19uDsiffvqp4wbJyclOdHS0KXtMTIx5rn9Mbi2/74+g6EOXvfQt4/nCCy+YPwxN8omJic6ePXvyP1/0QKzmz5/vREREmGQa6hj0gJaUlOQ0aNDALMMYGxvrjB49Ov/kxA0x+Cu7PhYtWpS/jSbvxx57zCwrWbNmTWfgwIHmgF10HxRcLnLSpEnmu1iwvkIVw4EDB0zyqFu3rvketW7d2pk6dapz+vRpV8TwyCOPmO+G/t3qd0W/575EYsP+R/kgN5UfclPoY7A9N9melxS56W+V9D+hHlUCAAAAgGDyzDU6AAAAAOBDQwcAAACA59DQAQAAAOA5NHQAAAAAeA4NHQAAAACeQ0MHAAAAgOfQ0AEAAADgOTR0AAAAAHgODR0gSEaMGCEDBgwIdTEAADDIS6joaOgAAAAA8BwaOkAJrVy5Ujp06CA1atSQevXqSY8ePWTq1KnywQcfyNq1a6VSpUrmkZaWZrY/ePCg3H///RIVFSV169aV/v37y/79+4v1uM2aNUsaNGggderUkXHjxsmlS5dCGCUAwBbkJcC/qgFeB+BHVlaWDBkyRObNmycDBw6UM2fOyObNm2XYsGFy4MAByc7OlkWLFpltNXlcvnxZevXqJXFxcWa7qlWryksvvSS9e/eWnTt3SlhYmNl2w4YNEh4ebpKQJpuRI0eaZPXyyy+HOGIAgJuRl4DAaOgAJUwoubm5MmjQIImNjTWvaS+a0p60ixcvSuPGjfO3/+ijjyQvL08WLlxoetOUJhztRdPkkZSUZF7TxPL+++9LzZo1pX379jJ79mzTG/fiiy9K5coMvAIA/CMvAYHxTQVKoGPHjpKYmGiSyODBg+W9996TkydPBtx+x44dkpmZKREREVK7dm3z0B61CxcuyN69ewv9Xk0mPtrTdvbsWTO9AACAQMhLQGCM6AAlUKVKFVm/fr189913sm7dOlmwYIE899xzsm3bNr/ba1Lo3LmzLFmypNh7Ou8ZAIDrQV4CAqOhA5SQDvXHx8ebR0pKipkqsHr1ajPMf+XKlULbdurUSZYvXy4NGzY0F3NerYctJyfHTDNQW7duNb1szZo1K/N4AAB2Iy8B/jF1DSgB7SF75ZVXJCMjw1zkuWrVKjl+/Li0a9dOWrRoYS7k3LNnj5w4ccJc8Dl06FCpX7++WdFGL/rct2+fmQP9+OOPy6FDh/J/r65kM2rUKNm9e7d88cUXMmPGDJk4cSLzoAEAV0VeAgJjRAcoAe39Sk9PlzfeeMOsZKO9ZvPnz5c+ffrI7bffbpKF/tSpAampqdK9e3ez/bRp08yForoaTkxMjJlPXbAnTZ+3adNGEhISzIWjuoLOzJkzQxorAMD9yEtAYJUcx3Gu8j6AMqb3Kzh16pSsWbMm1EUBAIC8BM9g/BEAAACA59DQAQAAAOA5TF0DAAAA4DmM6AAAAADwHBo6AAAAADyHhg4AAAAAz6GhAwAAAMBzaOgAAAAA8BwaOgAAAAA8h4YOAAAAAM+hoQMAAADAc2joAAAAABCv+R9aR5M5k92DvAAAAABJRU5ErkJggg=="
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 33
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 评估",
   "id": "d21fef8aa9e3e4f3"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-09T05:03:30.115312Z",
     "start_time": "2025-02-09T05:03:17.515823Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.load_state_dict(torch.load(f\"checkpoints/cnn-{activation}/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, test_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ],
   "id": "810a917a4476d48e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.1380\n",
      "accuracy: 0.9502\n"
     ]
    }
   ],
   "execution_count": 34
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "473e9bf72abd7608"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
